Пример #1
0
        protected override IEnumerable <Datum> Poll(CancellationToken cancellationToken)
        {
            List <Datum> data = new List <Datum>();

            if (HasValidAccessToken)
            {
                string[] missingPermissions = GetRequiredPermissionNames().Where(p => !AccessToken.CurrentAccessToken.Permissions.Contains(p)).ToArray();
                if (missingPermissions.Length > 0)
                {
                    ObtainAccessToken(missingPermissions);
                }
            }
            else
            {
                ObtainAccessToken(GetRequiredPermissionNames());
            }

            if (HasValidAccessToken)
            {
                ManualResetEvent        startWait     = new ManualResetEvent(false);
                List <ManualResetEvent> responseWaits = new List <ManualResetEvent>();
                Exception exception = null;  // can't throw exception from within the UI thread -- it will crash the app. use this variable to check whether an exception did occur.

                SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(() =>
                {
                    try
                    {
                        GraphRequestConnection requestConnection = new GraphRequestConnection();

                        #region add requests to connection
                        foreach (Tuple <string, List <string> > edgeFieldQuery in GetEdgeFieldQueries())
                        {
                            NSDictionary parameters = null;
                            if (edgeFieldQuery.Item2.Count > 0)
                            {
                                parameters = new NSDictionary("fields", string.Concat(edgeFieldQuery.Item2.Select(field => field + ",")).Trim(','));
                            }

                            GraphRequest request = new GraphRequest(
                                "me" + (edgeFieldQuery.Item1 == null ? "" : "/" + edgeFieldQuery.Item1),
                                parameters,
                                AccessToken.CurrentAccessToken.TokenString,
                                null,
                                "GET");

                            ManualResetEvent responseWait = new ManualResetEvent(false);

                            requestConnection.AddRequest(request, (connection, result, error) =>
                            {
                                if (error == null)
                                {
                                    FacebookDatum datum = new FacebookDatum(DateTimeOffset.UtcNow);

                                    #region set datum properties
                                    NSDictionary resultDictionary = result as NSDictionary;
                                    bool valuesSet = false;
                                    foreach (string resultKey in resultDictionary.Keys.Select(k => k.ToString()))
                                    {
                                        PropertyInfo property;
                                        if (FacebookDatum.TryGetProperty(resultKey, out property))
                                        {
                                            object value = null;

                                            if (property.PropertyType == typeof(string))
                                            {
                                                value = resultDictionary[resultKey].ToString();
                                            }
                                            else if (property.PropertyType == typeof(bool?))
                                            {
                                                int parsedBool;
                                                if (int.TryParse(resultDictionary[resultKey].ToString(), out parsedBool))
                                                {
                                                    value = parsedBool == 1 ? true : false;
                                                }
                                            }
                                            else if (property.PropertyType == typeof(DateTimeOffset?))
                                            {
                                                DateTimeOffset parsedDateTimeOffset;
                                                if (DateTimeOffset.TryParse(resultDictionary[resultKey].ToString(), out parsedDateTimeOffset))
                                                {
                                                    value = parsedDateTimeOffset;
                                                }
                                            }
                                            else if (property.PropertyType == typeof(List <string>))
                                            {
                                                List <string> values = new List <string>();

                                                NSArray resultArray = resultDictionary[resultKey] as NSArray;
                                                for (nuint i = 0; i < resultArray.Count; ++i)
                                                {
                                                    values.Add(resultArray.GetItem <NSObject>(i).ToString());
                                                }

                                                value = values;
                                            }
                                            else
                                            {
                                                throw new SensusException("Unrecognized FacebookDatum property type:  " + property.PropertyType.ToString());
                                            }

                                            if (value != null)
                                            {
                                                property.SetValue(datum, value);
                                                valuesSet = true;
                                            }
                                        }
                                        else
                                        {
                                            SensusServiceHelper.Get().Logger.Log("Unrecognized key in Facebook result dictionary:  " + resultKey, LoggingLevel.Verbose, GetType());
                                        }
                                    }
                                    #endregion

                                    if (valuesSet)
                                    {
                                        data.Add(datum);
                                    }
                                }
                                else
                                {
                                    SensusServiceHelper.Get().Logger.Log("Error received while querying Facebook graph API:  " + error.Description, LoggingLevel.Normal, GetType());
                                }

                                SensusServiceHelper.Get().Logger.Log("Response for \"" + request.GraphPath + "\" has been processed.", LoggingLevel.Verbose, GetType());
                                responseWait.Set();
                            });

                            responseWaits.Add(responseWait);
                        }
                        #endregion

                        if (responseWaits.Count == 0)
                        {
                            exception = new Exception("Request connection contained zero requests.");
                        }
                        else
                        {
                            SensusServiceHelper.Get().Logger.Log("Starting request connection with " + responseWaits.Count + " requests.", LoggingLevel.Normal, GetType());
                            requestConnection.Start();
                        }

                        startWait.Set();
                    }
                    catch (Exception ex)
                    {
                        exception = new Exception("Error starting request connection:  " + ex.Message);
                    }
                });

                startWait.WaitOne();

                // wait for all responses to be processed
                foreach (ManualResetEvent responseWait in responseWaits)
                {
                    responseWait.WaitOne();
                }

                // if any exception occurred when running query, throw it now
                if (exception != null)
                {
                    throw exception;
                }
            }
            else
            {
                throw new Exception("Attempted to poll Facebook probe without a valid access token.");
            }

            return(data);
        }
Пример #2
0
        public ScriptInputsPage(InputGroup inputGroup, List <InputGroup> previousInputGroups)
        {
            _inputGroup = inputGroup;

            Title = "Inputs";

            _inputsList = new ListView();
            _inputsList.ItemTemplate = new DataTemplate(typeof(TextCell));
            _inputsList.ItemTemplate.SetBinding(TextCell.TextProperty, new Binding(".", stringFormat: "{0}"));
            _inputsList.ItemTapped += async(o, e) =>
            {
                if (_inputsList.SelectedItem == null)
                {
                    return;
                }

                Input selectedInput = _inputsList.SelectedItem as Input;
                int   selectedIndex = inputGroup.Inputs.IndexOf(selectedInput);

                List <string> actions = new List <string>();

                if (selectedIndex > 0)
                {
                    actions.Add("Move Up");
                }

                if (selectedIndex < inputGroup.Inputs.Count - 1)
                {
                    actions.Add("Move Down");
                }

                actions.Add("Edit");

                if (previousInputGroups != null && previousInputGroups.Select(previousInputGroup => previousInputGroup.Inputs.Count).Sum() > 0)
                {
                    actions.Add("Add Display Condition");
                }

                if (selectedInput.DisplayConditions.Count > 0)
                {
                    actions.AddRange(new string[] { "View Display Conditions" });
                }

                actions.Add("Delete");

                string selectedAction = await DisplayActionSheet(selectedInput.Name, "Cancel", null, actions.ToArray());

                if (selectedAction == "Move Up")
                {
                    inputGroup.Inputs.Move(selectedIndex, selectedIndex - 1);
                }
                else if (selectedAction == "Move Down")
                {
                    inputGroup.Inputs.Move(selectedIndex, selectedIndex + 1);
                }
                else if (selectedAction == "Edit")
                {
                    ScriptInputPage inputPage = new ScriptInputPage(selectedInput);
                    inputPage.Disappearing += (oo, ee) =>
                    {
                        Bind();
                    };

                    await Navigation.PushAsync(inputPage);

                    _inputsList.SelectedItem = null;
                }
                else if (selectedAction == "Add Display Condition")
                {
                    string abortMessage = "Condition is not complete. Abort?";

                    SensusServiceHelper.Get().PromptForInputsAsync("Display Condition", new Input[]
                    {
                        new ItemPickerPageInput("Input:", previousInputGroups.SelectMany(previousInputGroup => previousInputGroup.Inputs.Cast <object>()).ToList()),
                        new ItemPickerPageInput("Condition:", Enum.GetValues(typeof(InputValueCondition)).Cast <object>().ToList()),
                        new ItemPickerPageInput("Conjunctive/Disjunctive:", new object[] { "Conjunctive", "Disjunctive" }.ToList())
                    },
                                                                   null,
                                                                   true,
                                                                   null,
                                                                   null,
                                                                   abortMessage,
                                                                   null,
                                                                   false,
                                                                   inputs =>
                    {
                        if (inputs == null)
                        {
                            return;
                        }

                        if (inputs.All(input => input.Valid))
                        {
                            Input conditionInput          = ((inputs[0] as ItemPickerPageInput).Value as IEnumerable <object>).First() as Input;
                            InputValueCondition condition = (InputValueCondition)((inputs[1] as ItemPickerPageInput).Value as IEnumerable <object>).First();
                            bool conjunctive = ((inputs[2] as ItemPickerPageInput).Value as IEnumerable <object>).First().Equals("Conjunctive");

                            if (condition == InputValueCondition.IsComplete)
                            {
                                selectedInput.DisplayConditions.Add(new InputDisplayCondition(conditionInput, condition, null, conjunctive));
                            }
                            else
                            {
                                Regex uppercaseSplitter = new Regex(@"
                                    (?<=[A-Z])(?=[A-Z][a-z]) |
                                    (?<=[^A-Z])(?=[A-Z]) |
                                    (?<=[A-Za-z])(?=[^A-Za-z])", RegexOptions.IgnorePatternWhitespace);

                                // show the user a required copy of the condition input and prompt for the condition value
                                Input conditionInputCopy = conditionInput.Copy();
                                conditionInputCopy.DisplayConditions.Clear();
                                conditionInputCopy.LabelText = "Value that " + conditionInputCopy.Name + " " + uppercaseSplitter.Replace(condition.ToString(), " ").ToLower() + ":";
                                conditionInputCopy.Required  = true;

                                SensusServiceHelper.Get().PromptForInputAsync("Display Condition",
                                                                              conditionInputCopy,
                                                                              null,
                                                                              true,
                                                                              "OK",
                                                                              null,
                                                                              abortMessage,
                                                                              null,
                                                                              false,
                                                                              input =>
                                {
                                    if (input == null)
                                    {
                                        return;
                                    }

                                    if (input.Valid)
                                    {
                                        selectedInput.DisplayConditions.Add(new InputDisplayCondition(conditionInput, condition, input.Value, conjunctive));
                                    }
                                });
                            }
                        }
                    });
                }
                else if (selectedAction == "View Display Conditions")
                {
                    await Navigation.PushAsync(new ViewTextLinesPage("Display Conditions", selectedInput.DisplayConditions.Select(displayCondition => displayCondition.ToString()).ToList(), null, () =>
                    {
                        selectedInput.DisplayConditions.Clear();
                    }));
                }
                else if (selectedAction == "Delete")
                {
                    if (await DisplayAlert("Delete " + selectedInput.Name + "?", "This action cannot be undone.", "Delete", "Cancel"))
                    {
                        _inputGroup.Inputs.Remove(selectedInput);
                        _inputsList.SelectedItem = null;  // manually reset, since it isn't done automatically.
                    }
                }
            };

            ToolbarItems.Add(new ToolbarItem(null, "plus.png", async() =>
            {
                List <Input> inputs = Assembly.GetExecutingAssembly()
                                      .GetTypes()
                                      .Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(Input)))
                                      .Select(t => Activator.CreateInstance(t))
                                      .Cast <Input>()
                                      .OrderBy(i => i.Name)
                                      .ToList();

                string cancelButtonName = "Cancel";
                string selected         = await DisplayActionSheet("Select Input Type", cancelButtonName, null, inputs.Select((input, index) => (index + 1) + ") " + input.Name).ToArray());
                if (!string.IsNullOrWhiteSpace(selected) && selected != cancelButtonName)
                {
                    Input input = inputs[int.Parse(selected.Substring(0, selected.IndexOf(")"))) - 1];

                    if (input is VoiceInput && inputGroup.Inputs.Count > 0 || !(input is VoiceInput) && inputGroup.Inputs.Any(i => i is VoiceInput))
                    {
                        SensusServiceHelper.Get().FlashNotificationAsync("Voice inputs must reside in groups by themselves.");
                    }
                    else
                    {
                        inputGroup.Inputs.Add(input);

                        ScriptInputPage inputPage = new ScriptInputPage(input);
                        inputPage.Disappearing   += (o, e) =>
                        {
                            Bind();
                        };

                        await Navigation.PushAsync(inputPage);
                    }
                }
            }));

            Bind();
            Content = _inputsList;
        }
Пример #3
0
 private async Task FlashChartDataCountAsync(Probe probe)
 {
     await SensusServiceHelper.Get().FlashNotificationAsync("Displaying " + probe.MaxChartDataCount + " point" + (probe.MaxChartDataCount == 1 ? "" : "s") + ".");
 }
Пример #4
0
        public ProtocolsPage()
        {
            Title = "Your Studies";

            _protocolsList = new ListView();
            _protocolsList.ItemTemplate = new DataTemplate(typeof(TextCell));
            _protocolsList.ItemTemplate.SetBinding(TextCell.TextProperty, new Binding(".", converter: new ProtocolNameValueConverter()));
            _protocolsList.ItemTemplate.SetBinding(TextCell.TextColorProperty, new Binding(".", converter: new ProtocolColorValueConverter()));
            _protocolsList.ItemTapped += async(o, e) =>
            {
                if (_protocolsList.SelectedItem == null)
                {
                    return;
                }

                Protocol selectedProtocol = _protocolsList.SelectedItem as Protocol;

                List <string> actions = new List <string>();

                actions.Add(selectedProtocol.Running ? "Stop" : "Start");
                actions.Add("View Data");

                if (selectedProtocol.Running)
                {
                    actions.Add("Display Participation");
                }

                if (selectedProtocol.RemoteDataStore?.CanRetrieveCommittedData ?? false)
                {
                    actions.Add("Scan Participation Barcode");
                }

                actions.AddRange(new string[] { "Edit", "Copy", "Share" });

                List <Protocol> groupableProtocols = SensusServiceHelper.Get().RegisteredProtocols.Where(registeredProtocol => registeredProtocol != selectedProtocol && registeredProtocol.Groupable && registeredProtocol.GroupedProtocols.Count == 0).ToList();
                if (selectedProtocol.Groupable)
                {
                    if (selectedProtocol.GroupedProtocols.Count == 0 && groupableProtocols.Count > 0)
                    {
                        actions.Add("Group");
                    }
                    else if (selectedProtocol.GroupedProtocols.Count > 0)
                    {
                        actions.Add("Ungroup");
                    }
                }

                if (selectedProtocol.Running)
                {
                    actions.Add("Status");
                }

                if (!selectedProtocol.Running && selectedProtocol.ScheduledStartCallbackId != null)
                {
                    actions.Remove("Start");
                    actions.Insert(0, "Cancel Scheduled Start");
                }

                actions.Add("Delete");

                string selectedAction = await DisplayActionSheet(selectedProtocol.Name, "Cancel", null, actions.ToArray());

                // must reset the protocol selection manually
                Device.BeginInvokeOnMainThread(() =>
                {
                    _protocolsList.SelectedItem = null;
                });

                if (selectedAction == "Start")
                {
                    selectedProtocol.StartWithUserAgreementAsync(null, () =>
                    {
                        // rebind to pick up color and running status changes
                        Refresh();
                    });
                }
                else if (selectedAction == "Cancel Scheduled Start")
                {
                    if (await DisplayAlert("Confirm Cancel", "Are you sure you want to cancel " + selectedProtocol.Name + "?", "Yes", "No"))
                    {
                        selectedProtocol.CancelScheduledStart();

                        // rebind to pick up color and running status changes
                        Refresh();
                    }
                }
                else if (selectedAction == "Stop")
                {
                    if (await DisplayAlert("Confirm Stop", "Are you sure you want to stop " + selectedProtocol.Name + "?", "Yes", "No"))
                    {
                        selectedProtocol.StopAsync(() =>
                        {
                            // rebind to pick up color and running status changes
                            Refresh();
                        });
                    }
                }
                else if (selectedAction == "View Data")
                {
                    await Navigation.PushAsync(new ProbesViewPage(selectedProtocol));
                }
                else if (selectedAction == "Display Participation")
                {
                    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

                    // pop up wait screen while we submit the participation reward datum
                    SensusServiceHelper.Get().PromptForInputsAsync(
                        null,
                        new [] { new InputGroup {
                                     Name = "Please Wait", Inputs = { new LabelOnlyInput("Submitting participation information.", false) }
                                 } },
                        cancellationTokenSource.Token,
                        false,
                        "Cancel",
                        null,
                        null,
                        null,
                        false,
                        async() =>
                    {
                        // add participation reward datum to remote data store and commit immediately
                        ParticipationRewardDatum participationRewardDatum = new ParticipationRewardDatum(DateTimeOffset.UtcNow, selectedProtocol.Participation);

                        bool commitFailed;

                        try
                        {
                            commitFailed = !await selectedProtocol.RemoteDataStore.CommitAsync(participationRewardDatum, cancellationTokenSource.Token);
                        }
                        catch (Exception)
                        {
                            commitFailed = true;
                        }

                        if (commitFailed)
                        {
                            SensusServiceHelper.Get().FlashNotificationAsync("Failed to submit participation information to remote server. You will not be able to verify your participation at this time.");
                        }

                        // cancel the token to close the input above, but only if the token hasn't already been canceled.
                        if (!cancellationTokenSource.IsCancellationRequested)
                        {
                            cancellationTokenSource.Cancel();
                        }

                        Device.BeginInvokeOnMainThread(async() =>
                        {
                            // only show the QR code for the reward datum if the datum was committed to the remote data store and if the data store can retrieve it.
                            await Navigation.PushAsync(new ParticipationReportPage(selectedProtocol, participationRewardDatum, !commitFailed && (selectedProtocol.RemoteDataStore?.CanRetrieveCommittedData ?? false)));
                        });
                    },
                        inputs =>
                    {
                        // if the prompt was closed by the user instead of the cancellation token, cancel the token in order
                        // to cancel the remote data store commit. if the prompt was closed by the termination of the remote
                        // data store commit (i.e., by the canceled token), then don't cancel the token again.
                        if (!cancellationTokenSource.IsCancellationRequested)
                        {
                            cancellationTokenSource.Cancel();
                        }
                    });
                }
                else if (selectedAction == "Scan Participation Barcode")
                {
                    Result barcodeResult = null;

                    try
                    {
                        if (await SensusServiceHelper.Get().ObtainPermissionAsync(Permission.Camera) != PermissionStatus.Granted)
                        {
                            throw new Exception("Could not access camera.");
                        }

#if __ANDROID__ || __IOS__
                        ZXing.Mobile.MobileBarcodeScanner scanner = SensusServiceHelper.Get().BarcodeScanner;

                        if (scanner == null)
                        {
                            throw new Exception("Barcode scanner not present.");
                        }

                        scanner.TopText    = "Position a Sensus participation barcode in the window below, with the red line across the middle of the barcode.";
                        scanner.BottomText = "Sensus is not recording any of these images. Sensus is only trying to find a barcode.";
                        scanner.CameraUnsupportedMessage = "There is not a supported camera on this phone. Cannot scan barcode.";

                        barcodeResult = await scanner.Scan(new ZXing.Mobile.MobileBarcodeScanningOptions
                        {
                            PossibleFormats = new BarcodeFormat[] { BarcodeFormat.QR_CODE }.ToList()
                        });
#endif
                    }
                    catch (Exception ex)
                    {
                        string message = "Failed to scan barcode:  " + ex.Message;
                        SensusServiceHelper.Get().Logger.Log(message, LoggingLevel.Normal, GetType());
                        SensusServiceHelper.Get().FlashNotificationAsync(message);
                    }

                    if (barcodeResult != null)
                    {
                        CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

                        // pop up wait screen while we get the participation reward datum
                        SensusServiceHelper.Get().PromptForInputsAsync(
                            null,
                            new [] { new InputGroup {
                                         Name = "Please Wait", Inputs = { new LabelOnlyInput("Retrieving participation information.", false) }
                                     } },
                            cancellationTokenSource.Token,
                            false,
                            "Cancel",
                            null,
                            null,
                            null,
                            false,
                            async() =>
                        {
                            try
                            {
                                ParticipationRewardDatum participationRewardDatum = await selectedProtocol.RemoteDataStore.GetDatum <ParticipationRewardDatum>(barcodeResult.Text, cancellationTokenSource.Token);

                                // cancel the token to close the input above, but only if the token hasn't already been canceled.
                                if (!cancellationTokenSource.IsCancellationRequested)
                                {
                                    cancellationTokenSource.Cancel();
                                }

                                // ensure that the participation datum has not expired
                                if (participationRewardDatum.Timestamp > DateTimeOffset.UtcNow.AddSeconds(-SensusServiceHelper.PARTICIPATION_VERIFICATION_TIMEOUT_SECONDS))
                                {
                                    Device.BeginInvokeOnMainThread(async() =>
                                    {
                                        await Navigation.PushAsync(new VerifiedParticipationPage(selectedProtocol, participationRewardDatum));
                                    });
                                }
                                else
                                {
                                    SensusServiceHelper.Get().FlashNotificationAsync("Participation barcode has expired. The participant needs to regenerate the barcode.");
                                }
                            }
                            catch (Exception)
                            {
                                SensusServiceHelper.Get().FlashNotificationAsync("Failed to retrieve participation information.");
                            }
                            finally
                            {
                                // cancel the token to close the input above, but only if the token hasn't already been canceled. this will be
                                // used if an exception is thrown while getting the participation reward datum.
                                if (!cancellationTokenSource.IsCancellationRequested)
                                {
                                    cancellationTokenSource.Cancel();
                                }
                            }
                        },
                            inputs =>
                        {
                            // if the prompt was closed by the user instead of the cancellation token, cancel the token in order
                            // to cancel the datum retrieval. if the prompt was closed by the termination of the remote
                            // data store get (i.e., by the canceled token), then don't cancel the token again.
                            if (!cancellationTokenSource.IsCancellationRequested)
                            {
                                cancellationTokenSource.Cancel();
                            }
                        });
                    }
                }
                else if (selectedAction == "Edit")
                {
                    ExecuteActionUponProtocolAuthentication(selectedProtocol, () =>
                    {
                        Device.BeginInvokeOnMainThread(async() =>
                        {
                            ProtocolPage protocolPage  = new ProtocolPage(selectedProtocol);
                            protocolPage.Disappearing += (oo, ee) => Bind();          // rebind to pick up name changes
                            await Navigation.PushAsync(protocolPage);
                        });
                    }
                                                            );
                }
                else if (selectedAction == "Copy")
                {
                    selectedProtocol.CopyAsync(true, true);
                }
                else if (selectedAction == "Share")
                {
                    Action ShareSelectedProtocol = new Action(() =>
                    {
                        // make a deep copy of the selected protocol so we can reset it for sharing. don't reset the id of the protocol to keep
                        // it in the same study. also do not register the copy since we're just going to send it off.
                        selectedProtocol.CopyAsync(false, false, selectedProtocolCopy =>
                        {
                            // write protocol to file and share
                            string sharePath = SensusServiceHelper.Get().GetSharePath(".json");
                            selectedProtocolCopy.Save(sharePath);
                            SensusServiceHelper.Get().ShareFileAsync(sharePath, "Sensus Protocol:  " + selectedProtocolCopy.Name, "application/json");
                        });
                    });

                    if (selectedProtocol.Shareable)
                    {
                        ShareSelectedProtocol();
                    }
                    else
                    {
                        ExecuteActionUponProtocolAuthentication(selectedProtocol, ShareSelectedProtocol);
                    }
                }
                else if (selectedAction == "Group")
                {
                    SensusServiceHelper.Get().PromptForInputAsync("Group",
                                                                  new ItemPickerPageInput("Select Protocols", groupableProtocols.Cast <object>().ToList(), "Name")
                    {
                        Multiselect = true
                    },
                                                                  null, true, "Group", null, null, null, false,
                                                                  input =>
                    {
                        if (input == null)
                        {
                            SensusServiceHelper.Get().FlashNotificationAsync("No protocols grouped.");
                            return;
                        }

                        ItemPickerPageInput itemPickerPageInput = input as ItemPickerPageInput;

                        List <Protocol> selectedProtocols = (itemPickerPageInput.Value as List <object>).Cast <Protocol>().ToList();

                        if (selectedProtocols.Count == 0)
                        {
                            SensusServiceHelper.Get().FlashNotificationAsync("No protocols grouped.");
                        }
                        else
                        {
                            selectedProtocol.GroupedProtocols.AddRange(selectedProtocols);
                            SensusServiceHelper.Get().FlashNotificationAsync("Grouped \"" + selectedProtocol.Name + "\" with " + selectedProtocols.Count + " other protocol" + (selectedProtocols.Count == 1 ? "" : "s") + ".");
                        }
                    });
                }
                else if (selectedAction == "Ungroup")
                {
                    if (await DisplayAlert("Ungroup " + selectedProtocol.Name + "?", "This protocol is currently grouped with the following other protocols:" + Environment.NewLine + Environment.NewLine + string.Concat(selectedProtocol.GroupedProtocols.Select(protocol => protocol.Name + Environment.NewLine)), "Ungroup", "Cancel"))
                    {
                        selectedProtocol.GroupedProtocols.Clear();
                    }
                }
                else if (selectedAction == "Status")
                {
                    if (SensusServiceHelper.Get().ProtocolShouldBeRunning(selectedProtocol))
                    {
                        await selectedProtocol.TestHealthAsync(true);

                        Device.BeginInvokeOnMainThread(async() =>
                        {
                            if (selectedProtocol.MostRecentReport == null)
                            {
                                await DisplayAlert("No Report", "Status check failed.", "OK");
                            }
                            else
                            {
                                await Navigation.PushAsync(new ViewTextLinesPage("Protocol Status", selectedProtocol.MostRecentReport.ToString().Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).ToList(), null, null));
                            }
                        });
                    }
                    else
                    {
                        await DisplayAlert("Protocol Not Running", "Cannot check status of protocol when protocol is not running.", "OK");
                    }
                }
                else if (selectedAction == "Delete")
                {
                    if (await DisplayAlert("Delete " + selectedProtocol.Name + "?", "This action cannot be undone.", "Delete", "Cancel"))
                    {
                        selectedProtocol.DeleteAsync();
                    }
                }
            };

            Content = _protocolsList;

            ToolbarItems.Add(new ToolbarItem(null, "gear_wrench.png", async() =>
            {
                double shareDirectoryMB          = SensusServiceHelper.GetDirectorySizeMB(SensusServiceHelper.SHARE_DIRECTORY);
                string clearShareDirectoryAction = "Clear Share Directory (" + Math.Round(shareDirectoryMB, 1) + " MB)";

                List <string> buttons = new string[] { "New Protocol", "View Log", "View Points of Interest", clearShareDirectoryAction }.ToList();

                // stopping only makes sense on android, where we use a background service. on ios, there is no concept
                // of stopping the app other than the user or system terminating the app.
#if __ANDROID__
                buttons.Add("Stop Sensus");
#endif

                buttons.Add("About Sensus");

                string action = await DisplayActionSheet("Other Actions", "Back", null, buttons.ToArray());

                if (action == "New Protocol")
                {
                    Protocol.Create("New Protocol");
                }
                else if (action == "View Log")
                {
                    await Navigation.PushAsync(new ViewTextLinesPage("Log", SensusServiceHelper.Get().Logger.Read(200, true),
                                                                     () =>
                    {
                        string sharePath = null;
                        try
                        {
                            sharePath = SensusServiceHelper.Get().GetSharePath(".txt");
                            SensusServiceHelper.Get().Logger.CopyTo(sharePath);
                        }
                        catch (Exception)
                        {
                            sharePath = null;
                        }

                        if (sharePath != null)
                        {
                            SensusServiceHelper.Get().ShareFileAsync(sharePath, "Log:  " + Path.GetFileName(sharePath), "text/plain");
                        }
                    },
                                                                     () => SensusServiceHelper.Get().Logger.Clear()));
                }
                else if (action == "View Points of Interest")
                {
                    await Navigation.PushAsync(new PointsOfInterestPage(SensusServiceHelper.Get().PointsOfInterest));
                }
                else if (action == clearShareDirectoryAction)
                {
                    foreach (string sharePath in Directory.GetFiles(SensusServiceHelper.SHARE_DIRECTORY))
                    {
                        try
                        {
                            File.Delete(sharePath);
                        }
                        catch (Exception ex)
                        {
                            string errorMessage = "Failed to delete shared file \"" + Path.GetFileName(sharePath) + "\":  " + ex.Message;
                            SensusServiceHelper.Get().FlashNotificationAsync(errorMessage);
                            SensusServiceHelper.Get().Logger.Log(errorMessage, LoggingLevel.Normal, GetType());
                        }
                    }
                }
#if __ANDROID__
                else if (action == "Stop Sensus" && await DisplayAlert("Confirm", "Are you sure you want to stop Sensus? This will end your participation in all studies.", "Stop Sensus", "Go Back"))
                {
                    SensusServiceHelper.Get().StopProtocols();
                    (SensusServiceHelper.Get() as Android.IAndroidSensusServiceHelper)?.StopAndroidSensusService();
                }
#endif
                else if (action == "About Sensus")
                {
                    await DisplayAlert("About Sensus", "Version:  " + SensusServiceHelper.Get().Version, "OK");
                }
            }));

            Bind();

            System.Timers.Timer refreshTimer = new System.Timers.Timer(1000);

            refreshTimer.Elapsed += (sender, e) =>
            {
                Refresh();
            };

            Appearing += (sender, e) =>
            {
                refreshTimer.Start();
            };

            Disappearing += (sender, e) =>
            {
                refreshTimer.Stop();
            };
        }
Пример #5
0
 private void FlashChartDataCountAsync(Probe probe)
 {
     SensusServiceHelper.Get().FlashNotificationAsync("Displaying " + probe.MaxChartDataCount + " point" + (probe.MaxChartDataCount == 1 ? "" : "s") + ".", false, TimeSpan.FromSeconds(2));
 }
Пример #6
0
 public Java.Lang.Object Invoke(Java.Lang.Object p0)
 {
     SensusServiceHelper.Get().Logger.Log("Error while observing Estimote beacons:  " + p0, LoggingLevel.Normal, GetType());
     return(null);
 }
Пример #7
0
        /// <summary>
        /// Gets a GPS reading, reusing an old one if it isn't too old. Will block the current thread while waiting for a GPS reading. Should not
        /// be called from the main / UI thread, since GPS runs on main thread (will deadlock).
        /// </summary>
        /// <returns>The reading.</returns>
        /// <param name="maxReadingAgeForReuseMS">Maximum age of old reading to reuse (milliseconds).</param>
        /// <param name="cancellationToken">Cancellation token.</param>
        /// <param name="checkAndObtainPermission">Whether or not to check for and obtain permission for the reading. Note that, on Android, this
        /// may result in bringing the Sensus UI to the foreground. If you do not wish this to happen, then obtain the user's permission prior to
        /// calling this method.</param>
        public Position GetReading(int maxReadingAgeForReuseMS, CancellationToken cancellationToken, bool checkAndObtainPermission)
        {
            lock (_locker)
            {
                if (checkAndObtainPermission && SensusServiceHelper.Get().ObtainPermission(Permission.Location) != PermissionStatus.Granted)
                {
                    return(null);
                }

                // reuse existing reading if it isn't too old
                if (_reading != null && maxReadingAgeForReuseMS > 0)
                {
                    double readingAgeMS = (DateTimeOffset.UtcNow - _reading.Timestamp).TotalMilliseconds;
                    if (readingAgeMS <= maxReadingAgeForReuseMS)
                    {
                        SensusServiceHelper.Get().Logger.Log("Reusing previous GPS reading, which is " + readingAgeMS + "ms old (maximum = " + maxReadingAgeForReuseMS + "ms).", LoggingLevel.Verbose, GetType());
                        return(_reading);
                    }
                }

                if (_readingIsComing)
                {
                    SensusServiceHelper.Get().Logger.Log("A GPS reading is coming. Will wait for it.", LoggingLevel.Debug, GetType());
                }
                else
                {
                    _readingIsComing = true; // tell any subsequent, concurrent callers that we're taking a reading
                    _readingWait.Reset();    // make them wait

                    Task.Run(async() =>
                    {
                        try
                        {
                            SensusServiceHelper.Get().Logger.Log("Taking GPS reading.", LoggingLevel.Debug, GetType());

                            DateTimeOffset readingStart = DateTimeOffset.UtcNow;
                            _locator.DesiredAccuracy    = SensusServiceHelper.Get().GpsDesiredAccuracyMeters;
                            Position newReading         = await _locator.GetPositionAsync(TimeSpan.FromMilliseconds(_readingTimeoutMS), cancellationToken);
                            DateTimeOffset readingEnd   = DateTimeOffset.UtcNow;

                            if (newReading != null)
                            {
                                // create copy of new position to keep return references separate, since the same Position object is returned multiple times when a change listener is attached.
                                _reading = new Position(newReading);

                                SensusServiceHelper.Get().Logger.Log("GPS reading obtained in " + (readingEnd - readingStart).TotalSeconds + " seconds.", LoggingLevel.Verbose, GetType());
                            }
                        }
                        catch (Exception ex)
                        {
                            SensusServiceHelper.Get().Logger.Log("GPS reading failed:  " + ex.Message, LoggingLevel.Normal, GetType());
                            _reading = null;
                        }

                        _readingWait.Set();       // tell anyone waiting on the shared reading that it is ready
                        _readingIsComing = false; // direct any future calls to this method to get their own reading
                    });
                }
            }

            _readingWait.WaitOne(_readingTimeoutMS);

            if (_reading == null)
            {
                SensusServiceHelper.Get().Logger.Log("GPS reading is null.", LoggingLevel.Normal, GetType());
            }

            return(_reading);
        }
Пример #8
0
 private void StopRandomScriptTriggerCallbacks()
 {
     SensusServiceHelper.Get().Logger.Log("Stopping random script trigger thread.", LoggingLevel.Normal, GetType());
     SensusServiceHelper.Get().CancelRepeatingCallback(_randomTriggerCallbackId);
     _randomTriggerCallbackId = -1;
 }
Пример #9
0
        /// <summary>
        /// Initializes a new instance of the <see cref="ProtocolPage"/> class.
        /// </summary>
        /// <param name="protocol">Protocol to display.</param>
        public ProtocolPage(Protocol protocol)
        {
            _protocol = protocol;

            Title = "Protocol";

            List <View> views = new List <View>();

            views.AddRange(UiProperty.GetPropertyStacks(_protocol));

            #region data stores
            string localDataStoreSize = _protocol.LocalDataStore?.SizeDescription;

            Button editLocalDataStoreButton = new Button
            {
                Text              = "Local Data Store" + (localDataStoreSize == null ? "" : " (" + localDataStoreSize + ")"),
                FontSize          = 20,
                HorizontalOptions = LayoutOptions.FillAndExpand,
                IsEnabled         = !_protocol.Running
            };

            editLocalDataStoreButton.Clicked += async(o, e) =>
            {
                if (_protocol.LocalDataStore != null)
                {
                    DataStore copy = _protocol.LocalDataStore.Copy();
                    if (copy == null)
                    {
                        SensusServiceHelper.Get().FlashNotificationAsync("Failed to edit data store.");
                    }
                    else
                    {
                        await Navigation.PushAsync(new DataStorePage(_protocol, copy, true, false));
                    }
                }
            };

            Button createLocalDataStoreButton = new Button
            {
                Text              = "+",
                FontSize          = 20,
                HorizontalOptions = LayoutOptions.End,
                IsEnabled         = !_protocol.Running
            };

            createLocalDataStoreButton.Clicked += (o, e) => CreateDataStore(true);

            StackLayout localDataStoreStack = new StackLayout
            {
                Orientation       = StackOrientation.Horizontal,
                HorizontalOptions = LayoutOptions.FillAndExpand,
                Children          = { editLocalDataStoreButton, createLocalDataStoreButton }
            };

            views.Add(localDataStoreStack);

            Button editRemoteDataStoreButton = new Button
            {
                Text              = "Remote Data Store",
                FontSize          = 20,
                HorizontalOptions = LayoutOptions.FillAndExpand,
                IsEnabled         = !_protocol.Running
            };

            editRemoteDataStoreButton.Clicked += async(o, e) =>
            {
                if (_protocol.RemoteDataStore != null)
                {
                    DataStore copy = _protocol.RemoteDataStore.Copy();
                    if (copy == null)
                    {
                        SensusServiceHelper.Get().FlashNotificationAsync("Failed to edit data store.");
                    }
                    else
                    {
                        await Navigation.PushAsync(new DataStorePage(_protocol, copy, false, false));
                    }
                }
            };

            Button createRemoteDataStoreButton = new Button
            {
                Text              = "+",
                FontSize          = 20,
                HorizontalOptions = LayoutOptions.End,
                IsEnabled         = !_protocol.Running
            };

            createRemoteDataStoreButton.Clicked += (o, e) => CreateDataStore(false);

            StackLayout remoteDataStoreStack = new StackLayout
            {
                Orientation       = StackOrientation.Horizontal,
                HorizontalOptions = LayoutOptions.FillAndExpand,
                Children          = { editRemoteDataStoreButton, createRemoteDataStoreButton }
            };

            views.Add(remoteDataStoreStack);
            #endregion

            #region points of interest
            Button pointsOfInterestButton = new Button
            {
                Text     = "Points of Interest",
                FontSize = 20
            };

            pointsOfInterestButton.Clicked += async(o, e) =>
            {
                await Navigation.PushAsync(new PointsOfInterestPage(_protocol.PointsOfInterest));
            };

            views.Add(pointsOfInterestButton);
            #endregion

            #region view probes
            Button viewProbesButton = new Button
            {
                Text     = "Probes",
                FontSize = 20
            };

            viewProbesButton.Clicked += async(o, e) =>
            {
                await Navigation.PushAsync(new ProbesEditPage(_protocol));
            };

            views.Add(viewProbesButton);
            #endregion

            _protocolRunningChangedAction = (o, running) =>
            {
                SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(() =>
                {
                    editLocalDataStoreButton.IsEnabled = createLocalDataStoreButton.IsEnabled = editRemoteDataStoreButton.IsEnabled = createRemoteDataStoreButton.IsEnabled = !running;
                });
            };

            StackLayout stack = new StackLayout
            {
                Orientation     = StackOrientation.Vertical,
                VerticalOptions = LayoutOptions.FillAndExpand
            };

            foreach (View view in views)
            {
                stack.Children.Add(view);
            }

            Button lockButton = new Button
            {
                Text              = _protocol.LockPasswordHash == "" ? "Lock" : "Unlock",
                FontSize          = 20,
                HorizontalOptions = LayoutOptions.FillAndExpand
            };

            lockButton.Clicked += (o, e) =>
            {
                if (lockButton.Text == "Lock")
                {
                    SensusServiceHelper.Get().PromptForInputAsync(
                        "Lock Protocol",
                        new SingleLineTextInput("Password:"******"Please enter a non-empty password.");
                        }
                        else
                        {
                            _protocol.LockPasswordHash = SensusServiceHelper.Get().GetHash(password);
                            SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(() => lockButton.Text = "Unlock");
                        }
                    });
                }
                else if (lockButton.Text == "Unlock")
                {
                    _protocol.LockPasswordHash = "";
                    lockButton.Text            = "Lock";
                }
            };

            stack.Children.Add(lockButton);

            Content = new ScrollView
            {
                Content = stack
            };
        }
Пример #10
0
        public ScriptProbe()
        {
            _triggers                     = new ObservableCollection <Trigger>();
            _triggerHandler               = new Dictionary <Trigger, EventHandler <Tuple <Datum, Datum> > >();
            _script                       = new Script("Empty Script");
            _incompleteScripts            = new Queue <Script>();
            _rerunIncompleteScripts       = false;
            _scriptRerunCallbackId        = -1;
            _scriptRerunDelayMS           = 60000;
            _maxScriptAgeMinutes          = 10;
            _numScriptsAgedOut            = 0;
            _triggerRandomly              = false;
            _randomTriggerCallbackId      = -1;
            _randomTriggerDelayMaxMinutes = 1;
            _random                       = new Random();

            _triggers.CollectionChanged += (o, e) =>
            {
                if (e.Action == NotifyCollectionChangedAction.Add)
                {
                    foreach (Trigger addedTrigger in e.NewItems)
                    {
                        // ignore duplicate triggers -- the user should delete and re-add them instead.
                        if (_triggerHandler.ContainsKey(addedTrigger))
                        {
                            return;
                        }

                        EventHandler <Tuple <Datum, Datum> > handler = (oo, prevCurrDatum) =>
                        {
                            // must be running and must have a current datum
                            lock (_locker)
                                if (!Running || prevCurrDatum.Item2 == null)
                                {
                                    return;
                                }

                            Datum prevDatum = prevCurrDatum.Item1;
                            Datum currDatum = prevCurrDatum.Item2;

                            // get the object that might trigger the script
                            object datumValue = addedTrigger.DatumProperty.GetValue(currDatum);
                            if (datumValue == null)
                            {
                                SensusServiceHelper.Get().Logger.Log("Trigger error:  Value of datum property " + addedTrigger.DatumPropertyName + " was null.", LoggingLevel.Normal, GetType());
                                return;
                            }

                            // if we're triggering based on datum value changes instead of absolute values, calculate the change now
                            if (addedTrigger.Change)
                            {
                                if (prevDatum == null)
                                {
                                    return;
                                }

                                try { datumValue = Convert.ToDouble(datumValue) - Convert.ToDouble(addedTrigger.DatumProperty.GetValue(prevDatum)); }
                                catch (Exception ex)
                                {
                                    SensusServiceHelper.Get().Logger.Log("Trigger error:  Failed to convert datum values to doubles:  " + ex.Message, LoggingLevel.Normal, GetType());
                                    return;
                                }
                            }

                            if (addedTrigger.FireFor(datumValue))
                            {
                                RunScriptAsync(_script.Copy(), prevDatum, currDatum);          // run a copy of the pristine script, since it will be filled in when run.
                            }
                        };

                        addedTrigger.Probe.MostRecentDatumChanged += handler;
                        _triggerHandler.Add(addedTrigger, handler);
                    }
                }
                else if (e.Action == NotifyCollectionChangedAction.Remove)
                {
                    foreach (Trigger removedTrigger in e.OldItems)
                    {
                        if (_triggerHandler.ContainsKey(removedTrigger))
                        {
                            removedTrigger.Probe.MostRecentDatumChanged -= _triggerHandler[removedTrigger];
                            _triggerHandler.Remove(removedTrigger);
                        }
                    }
                }
            };
        }
Пример #11
0
 private void StopScriptRerunCallbacks()
 {
     SensusServiceHelper.Get().Logger.Log("Stopping script rerun callbacks.", LoggingLevel.Normal, GetType());
     SensusServiceHelper.Get().CancelRepeatingCallback(_scriptRerunCallbackId);
     _scriptRerunCallbackId = -1;
 }
 public Java.Lang.Object Invoke(Java.Lang.Object p0)
 {
     SensusServiceHelper.Get().Logger.Log("Error during indoor positioning:  " + p0, LoggingLevel.Normal, GetType());
     return(null);
 }
Пример #13
0
        private bool UpdateFences(IFenceUpdateRequest updateRequest)
        {
            ManualResetEvent updateWait = new ManualResetEvent(false);

            bool success = false;

            try
            {
                // update fences is asynchronous
                Awareness.FenceApi.UpdateFences(_awarenessApiClient, updateRequest).SetResultCallback <Statuses>(status =>
                {
                    try
                    {
                        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);
                        }
                    }
                    catch (Exception ex)
                    {
                        SensusServiceHelper.Get().Logger.Log("Exception while processing update status:  " + ex, LoggingLevel.Normal, GetType());
                    }
                    finally
                    {
                        // ensure that wait is always set
                        updateWait.Set();
                    }
                });
            }
            // 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());
                updateWait.Set();
            }

            // 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.
            if (!updateWait.WaitOne(TimeSpan.FromSeconds(60)))
            {
                SensusServiceHelper.Get().Logger.Log("Timed out while updating fences.", LoggingLevel.Normal, GetType());
            }

            return(success);
        }
Пример #14
0
        private void ObtainAccessToken(string[] permissionNames)
        {
            lock (LoginLocker)
            {
                if (HasValidAccessToken)
                {
                    SensusServiceHelper.Get().Logger.Log("Already have valid Facebook access token. No need to initialize.", LoggingLevel.Normal, GetType());
                    return;
                }

                ManualResetEvent loginWait        = new ManualResetEvent(false);
                bool             loginCancelled   = false;
                string           accessTokenError = null;

                SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(() =>
                {
                    try
                    {
                        LoginManagerRequestTokenHandler loginResultHandler = new LoginManagerRequestTokenHandler((loginResult, error) =>
                        {
                            if (error == null && loginResult.Token != null)
                            {
                                SensusServiceHelper.Get().Logger.Log("Facebook login succeeded.", LoggingLevel.Normal, GetType());
                                AccessToken.CurrentAccessToken = loginResult.Token;
                                loginWait.Set();
                            }
                            else if (loginResult != null && loginResult.IsCancelled)
                            {
                                SensusServiceHelper.Get().Logger.Log("Facebook login cancelled.", LoggingLevel.Normal, GetType());
                                loginCancelled = true;
                                loginWait.Set();
                            }
                            else
                            {
                                SensusServiceHelper.Get().Logger.Log("Facebook login failed.", LoggingLevel.Normal, GetType());
                                loginWait.Set();
                            }
                        });

                        new LoginManager().LogInWithReadPermissions(permissionNames, UIApplication.SharedApplication.KeyWindow.RootViewController, loginResultHandler);
                    }
                    catch (Exception ex)
                    {
                        SensusServiceHelper.Get().Logger.Log("Error while initializing Facebook SDK and/or logging in:  " + ex.Message, LoggingLevel.Normal, GetType());
                        accessTokenError = ex.Message;
                        loginWait.Set();
                    }
                });

                loginWait.WaitOne();

                if (accessTokenError != null)
                {
                    SensusServiceHelper.Get().Logger.Log("Error while initializing Facebook SDK and/or logging in:  " + accessTokenError, LoggingLevel.Normal, GetType());
                }

                if (!HasValidAccessToken)
                {
                    string message = "Failed to obtain access token.";
                    SensusServiceHelper.Get().Logger.Log(message, LoggingLevel.Normal, GetType());

                    // if the user cancelled the login, don't prompt them again
                    if (loginCancelled)
                    {
                        throw new NotSupportedException(message + " User cancelled login.");
                    }
                    // if the user did not cancel the login, allow the login to be presented again when the health test is run
                    else
                    {
                        throw new Exception(message);
                    }
                }
            }
        }
Пример #15
0
        protected override List <Datum> CommitData(List <Datum> data)
        {
            List <Datum> committedData = new List <Datum>();

            DateTimeOffset start = DateTimeOffset.UtcNow;

            foreach (Datum datum in data)
            {
                try
                {
                    if (datum is RunningAppsDatum)
                    {
                        _runningAppsTable.InsertAsync(datum as RunningAppsDatum).Wait();
                    }
                    else if (datum is SmsDatum)
                    {
                        _smsTable.InsertAsync(datum as SmsDatum).Wait();
                    }
                    else if (datum is TelephonyDatum)
                    {
                        _telephonyTable.InsertAsync(datum as TelephonyDatum).Wait();
                    }
                    else if (datum is BluetoothDeviceProximityDatum)
                    {
                        _bluetoothTable.InsertAsync(datum as BluetoothDeviceProximityDatum).Wait();
                    }
                    else if (datum is LightDatum)
                    {
                        _lightTable.InsertAsync(datum as LightDatum).Wait();
                    }
                    else if (datum is SoundDatum)
                    {
                        _soundTable.InsertAsync(datum as SoundDatum).Wait();
                    }
                    else if (datum is BatteryDatum)
                    {
                        _batteryTable.InsertAsync(datum as BatteryDatum).Wait();
                    }
                    else if (datum is ScreenDatum)
                    {
                        _screenTable.InsertAsync(datum as ScreenDatum).Wait();
                    }
                    else if (datum is AltitudeDatum)
                    {
                        _altitudeTable.InsertAsync(datum as AltitudeDatum).Wait();
                    }
                    else if (datum is CompassDatum)
                    {
                        _compassTable.InsertAsync(datum as CompassDatum).Wait();
                    }
                    else if (datum is LocationDatum)
                    {
                        _locationTable.InsertAsync(datum as LocationDatum).Wait();
                    }
                    else if (datum is AccelerometerDatum)
                    {
                        _accelerometerTable.InsertAsync(datum as AccelerometerDatum).Wait();
                    }
                    else if (datum is SpeedDatum)
                    {
                        _speedTable.InsertAsync(datum as SpeedDatum).Wait();
                    }
                    else if (datum is CellTowerDatum)
                    {
                        _cellTowerTable.InsertAsync(datum as CellTowerDatum).Wait();
                    }
                    else if (datum is WlanDatum)
                    {
                        _wlanTable.InsertAsync(datum as WlanDatum).Wait();
                    }
                    else if (datum is ScriptDatum)
                    {
                        _scriptTable.InsertAsync(datum as ScriptDatum).Wait();
                    }
                    else if (datum is ProtocolReport)
                    {
                        _protocolReportTable.InsertAsync(datum as ProtocolReport).Wait();
                    }
                    else
                    {
                        throw new DataStoreException("Unrecognized Azure table:  " + datum.GetType().FullName);
                    }

                    committedData.Add(datum);
                }
                catch (Exception ex)
                {
                    if (ex.Message == "Error: Could not insert the item because an item with that id already exists.")
                    {
                        committedData.Add(datum);
                    }
                    else
                    {
                        SensusServiceHelper.Get().Logger.Log("Failed to insert datum into Azure table:  " + ex.Message, LoggingLevel.Normal, GetType());
                    }
                }
            }

            SensusServiceHelper.Get().Logger.Log("Committed " + committedData.Count + " data items to Azure tables in " + (DateTimeOffset.UtcNow - start).TotalSeconds + " seconds.", LoggingLevel.Verbose, GetType());

            return(committedData);
        }
Пример #16
0
        /// <summary>
        /// Initializes a new instance of the <see cref="AddScriptTriggerPage"/> class.
        /// </summary>
        /// <param name="scriptRunner">Script runner to add trigger to.</param>
        public AddScriptTriggerPage(ScriptRunner scriptRunner)
        {
            _scriptRunner = scriptRunner;

            Title = "Add Trigger";

            Probe[] enabledProbes = _scriptRunner.Probe.Protocol.Probes.Where(p => p != _scriptRunner.Probe && p.Enabled).ToArray();

            if (!enabledProbes.Any())
            {
                Content = new Label {
                    Text = "No enabled probes. Please enable them before creating triggers.", FontSize = 20
                };

                return;
            }

            StackLayout contentLayout = new StackLayout
            {
                Orientation     = StackOrientation.Vertical,
                VerticalOptions = LayoutOptions.FillAndExpand
            };

            Label probeLabel = new Label
            {
                Text     = "Probe:",
                FontSize = 20,
                VerticalTextAlignment = TextAlignment.Center
            };

            Picker probePicker = new Picker {
                Title = "Select Probe", HorizontalOptions = LayoutOptions.FillAndExpand
            };

            foreach (Probe enabledProbe in enabledProbes)
            {
                probePicker.Items.Add(enabledProbe.DisplayName);
            }

            contentLayout.Children.Add(new StackLayout
            {
                Orientation       = StackOrientation.Horizontal,
                HorizontalOptions = LayoutOptions.FillAndExpand,
                Children          = { probeLabel, probePicker }
            });

            StackLayout triggerDefinitionLayout = new StackLayout
            {
                Orientation     = StackOrientation.Vertical,
                VerticalOptions = LayoutOptions.Start
            };

            contentLayout.Children.Add(triggerDefinitionLayout);

            bool       allowChangeCalculation = false;
            Switch     changeSwitch           = new Switch();
            bool       allowRegularExpression = false;
            Switch     regexSwitch            = new Switch();
            Switch     fireRepeatedlySwitch   = new Switch();
            TimePicker startTimePicker        = new TimePicker {
                HorizontalOptions = LayoutOptions.FillAndExpand
            };
            TimePicker endTimePicker = new TimePicker {
                HorizontalOptions = LayoutOptions.FillAndExpand
            };

            probePicker.SelectedIndexChanged += (o, e) =>
            {
                _selectedProbe         = null;
                _selectedDatumProperty = null;
                _conditionValue        = null;

                triggerDefinitionLayout.Children.Clear();

                if (probePicker.SelectedIndex < 0)
                {
                    return;
                }

                _selectedProbe = enabledProbes[probePicker.SelectedIndex];

                PropertyInfo[] datumProperties = _selectedProbe.DatumType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.GetCustomAttributes <ProbeTriggerProperty>().Any()).ToArray();

                if (datumProperties.Length == 0)
                {
                    return;
                }

                #region datum property picker
                Label datumPropertyLabel = new Label
                {
                    Text     = "Property:",
                    FontSize = 20,
                    VerticalTextAlignment = TextAlignment.Center
                };

                Picker datumPropertyPicker = new Picker {
                    Title = "Select Datum Property", HorizontalOptions = LayoutOptions.FillAndExpand
                };
                foreach (PropertyInfo datumProperty in datumProperties)
                {
                    ProbeTriggerProperty triggerProperty = datumProperty.GetCustomAttributes <ProbeTriggerProperty>().First();
                    datumPropertyPicker.Items.Add(triggerProperty.Name ?? datumProperty.Name);
                }

                triggerDefinitionLayout.Children.Add(new StackLayout
                {
                    Orientation       = StackOrientation.Horizontal,
                    HorizontalOptions = LayoutOptions.FillAndExpand,
                    Children          = { datumPropertyLabel, datumPropertyPicker }
                });
                #endregion

                #region condition picker (same for all datum types)
                Label conditionLabel = new Label
                {
                    Text     = "Condition:",
                    FontSize = 20,
                    VerticalTextAlignment = TextAlignment.Center
                };

                Picker conditionPicker = new Picker {
                    Title = "Select Condition", HorizontalOptions = LayoutOptions.FillAndExpand
                };
                TriggerValueCondition[] conditions = Enum.GetValues(typeof(TriggerValueCondition)) as TriggerValueCondition[];
                foreach (TriggerValueCondition condition in conditions)
                {
                    conditionPicker.Items.Add(condition.ToString());
                }

                conditionPicker.SelectedIndexChanged += (oo, ee) =>
                {
                    if (conditionPicker.SelectedIndex < 0)
                    {
                        return;
                    }

                    _selectedCondition = conditions[conditionPicker.SelectedIndex];
                };

                triggerDefinitionLayout.Children.Add(new StackLayout
                {
                    Orientation       = StackOrientation.Horizontal,
                    HorizontalOptions = LayoutOptions.FillAndExpand,
                    Children          = { conditionLabel, conditionPicker }
                });
                #endregion

                #region condition value for comparison, based on selected datum property -- includes change calculation (for double datum) and regex (for string datum)
                StackLayout conditionValueStack = new StackLayout
                {
                    Orientation       = StackOrientation.Vertical,
                    HorizontalOptions = LayoutOptions.FillAndExpand
                };

                triggerDefinitionLayout.Children.Add(conditionValueStack);

                datumPropertyPicker.SelectedIndexChanged += (oo, ee) =>
                {
                    _selectedDatumProperty = null;
                    _conditionValue        = null;

                    conditionValueStack.Children.Clear();

                    if (datumPropertyPicker.SelectedIndex < 0)
                    {
                        return;
                    }

                    _selectedDatumProperty = datumProperties[datumPropertyPicker.SelectedIndex];

                    ProbeTriggerProperty datumTriggerAttribute = _selectedDatumProperty.GetCustomAttribute <ProbeTriggerProperty>();

                    View conditionValueStackView = null;

                    if (datumTriggerAttribute is ListProbeTriggerProperty)
                    {
                        Picker conditionValuePicker = new Picker {
                            Title = "Select Condition Value", HorizontalOptions = LayoutOptions.FillAndExpand
                        };
                        object[] items = (datumTriggerAttribute as ListProbeTriggerProperty).Items;
                        foreach (object item in items)
                        {
                            conditionValuePicker.Items.Add(item.ToString());
                        }

                        conditionValuePicker.SelectedIndexChanged += (ooo, eee) =>
                        {
                            if (conditionValuePicker.SelectedIndex < 0)
                            {
                                return;
                            }

                            _conditionValue = items[conditionValuePicker.SelectedIndex];
                        };

                        conditionValueStackView = conditionValuePicker;
                    }
                    else if (datumTriggerAttribute is DoubleProbeTriggerProperty)
                    {
                        Entry entry = new Entry
                        {
                            Keyboard          = Keyboard.Numeric,
                            HorizontalOptions = LayoutOptions.FillAndExpand
                        };

                        entry.TextChanged += (ooo, eee) =>
                        {
                            double value;
                            if (double.TryParse(eee.NewTextValue, out value))
                            {
                                _conditionValue = value;
                            }
                        };

                        conditionValueStackView = entry;
                        allowChangeCalculation  = true;
                    }
                    else if (datumTriggerAttribute is StringProbeTriggerProperty)
                    {
                        Entry entry = new Entry
                        {
                            Keyboard          = Keyboard.Default,
                            HorizontalOptions = LayoutOptions.FillAndExpand
                        };

                        entry.TextChanged += (ooo, eee) => _conditionValue = eee.NewTextValue;

                        conditionValueStackView = entry;
                        allowRegularExpression  = true;
                    }
                    else if (datumTriggerAttribute is BooleanProbeTriggerProperty)
                    {
                        Switch booleanSwitch = new Switch();

                        booleanSwitch.Toggled += (ooo, eee) => _conditionValue = eee.Value;

                        conditionValueStackView = booleanSwitch;
                    }

                    Label conditionValueStackLabel = new Label
                    {
                        Text     = "Value:",
                        FontSize = 20,
                        VerticalTextAlignment = TextAlignment.Center
                    };

                    conditionValueStack.Children.Add(new StackLayout
                    {
                        Orientation       = StackOrientation.Horizontal,
                        HorizontalOptions = LayoutOptions.FillAndExpand,
                        Children          = { conditionValueStackLabel, conditionValueStackView }
                    });

                    #region change calculation
                    if (allowChangeCalculation)
                    {
                        Label changeLabel = new Label
                        {
                            Text     = "Change:",
                            FontSize = 20,
                            VerticalTextAlignment = TextAlignment.Center
                        };

                        changeSwitch.IsToggled = false;

                        conditionValueStack.Children.Add(new StackLayout
                        {
                            Orientation       = StackOrientation.Horizontal,
                            HorizontalOptions = LayoutOptions.FillAndExpand,
                            Children          = { changeLabel, changeSwitch }
                        });
                    }
                    #endregion

                    #region regular expression
                    if (allowRegularExpression)
                    {
                        Label regexLabel = new Label
                        {
                            Text     = "Regular Expression:",
                            FontSize = 20,
                            VerticalTextAlignment = TextAlignment.Center
                        };

                        regexSwitch.IsToggled = false;

                        conditionValueStack.Children.Add(new StackLayout
                        {
                            Orientation       = StackOrientation.Horizontal,
                            HorizontalOptions = LayoutOptions.FillAndExpand,
                            Children          = { regexLabel, regexSwitch }
                        });
                    }
                    #endregion
                };

                datumPropertyPicker.SelectedIndex = 0;
                #endregion

                #region fire repeatedly
                Label fireRepeatedlyLabel = new Label
                {
                    Text     = "Fire Repeatedly:",
                    FontSize = 20,
                    VerticalTextAlignment = TextAlignment.Center
                };

                fireRepeatedlySwitch.IsToggled = true;

                triggerDefinitionLayout.Children.Add(new StackLayout
                {
                    Orientation       = StackOrientation.Horizontal,
                    HorizontalOptions = LayoutOptions.FillAndExpand,
                    Children          = { fireRepeatedlyLabel, fireRepeatedlySwitch }
                });
                #endregion

                #region start/end times
                Label startTimeLabel = new Label
                {
                    Text     = "Start Time:",
                    FontSize = 20,
                    VerticalTextAlignment = TextAlignment.Center
                };

                startTimePicker.Time = new TimeSpan(0, 0, 0);

                triggerDefinitionLayout.Children.Add(new StackLayout
                {
                    Orientation       = StackOrientation.Horizontal,
                    HorizontalOptions = LayoutOptions.FillAndExpand,
                    Children          = { startTimeLabel, startTimePicker }
                });

                Label endTimeLabel = new Label
                {
                    Text     = "End Time:",
                    FontSize = 20,
                    VerticalTextAlignment = TextAlignment.Center
                };

                endTimePicker.Time = new TimeSpan(23, 59, 59);

                triggerDefinitionLayout.Children.Add(new StackLayout
                {
                    Orientation       = StackOrientation.Horizontal,
                    HorizontalOptions = LayoutOptions.FillAndExpand,
                    Children          = { endTimeLabel, endTimePicker }
                });
                #endregion
            };

            probePicker.SelectedIndex = 0;

            Button okButton = new Button
            {
                Text            = "OK",
                FontSize        = 20,
                VerticalOptions = LayoutOptions.Start
            };

            okButton.Clicked += async(o, e) =>
            {
                try
                {
                    _scriptRunner.Triggers.Add(new Probes.User.Scripts.Trigger(_selectedProbe, _selectedDatumProperty, _selectedCondition, _conditionValue, allowChangeCalculation && changeSwitch.IsToggled, fireRepeatedlySwitch.IsToggled, allowRegularExpression && regexSwitch.IsToggled, startTimePicker.Time, endTimePicker.Time));
                    await Navigation.PopAsync();
                }
                catch (Exception ex)
                {
                    await SensusServiceHelper.Get().FlashNotificationAsync($"Failed to add trigger:  {ex.Message}");

                    SensusServiceHelper.Get().Logger.Log($"Failed to add trigger:  {ex.Message}", LoggingLevel.Normal, GetType());
                }
            };

            contentLayout.Children.Add(okButton);

            Content = new ScrollView
            {
                Content = contentLayout
            };
        }
Пример #17
0
        protected override void InternalStart()
        {
            lock (_locker)
            {
                base.InternalStart();

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

                _callback = new ScheduledCallback(GetType().FullName + " Poll", (callbackId, cancellationToken, letDeviceSleepCallback) =>
                {
                    return(Task.Run(async() =>
                    {
                        if (Running)
                        {
                            _isPolling = true;

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

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

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

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

                            _isPolling = false;
                        }
                    }));
                }, TimeSpan.FromMinutes(_pollingTimeoutMinutes), userNotificationMessage);

#if __IOS__
                if (_significantChangePoll)
                {
                    _locationManager.RequestAlwaysAuthorization();
                    _locationManager.DistanceFilter = 5.0;
                    _locationManager.PausesLocationUpdatesAutomatically = false;
                    _locationManager.AllowsBackgroundLocationUpdates    = true;

                    if (CLLocationManager.LocationServicesEnabled)
                    {
                        _locationManager.StartMonitoringSignificantLocationChanges();
                    }
                    else
                    {
                        SensusServiceHelper.Get().Logger.Log("Location services not enabled.", LoggingLevel.Normal, GetType());
                    }
                }

                // schedule the callback if we're not doing significant-change polling, or if we are but the latter doesn't override the former.
                if (!_significantChangePoll || !_significantChangeOverrideScheduledPolls)
                {
                    _pollCallbackId = SensusServiceHelper.Get().ScheduleRepeatingCallback(_callback, 0, _pollingSleepDurationMS, POLL_CALLBACK_LAG);
                }
#elif __ANDROID__
                _pollCallbackId = SensusServiceHelper.Get().ScheduleRepeatingCallback(_callback, 0, _pollingSleepDurationMS, POLL_CALLBACK_LAG);
#endif
            }
        }
Пример #18
0
        public ProtocolsPage()
        {
            Title = "Your Studies";

            _protocolsList = new ListView(ListViewCachingStrategy.RecycleElement);
            _protocolsList.ItemTemplate = new DataTemplate(typeof(TextCell));
            _protocolsList.ItemTemplate.SetBinding(TextCell.TextProperty, nameof(Protocol.Caption));
            _protocolsList.ItemTemplate.SetBinding(TextCell.DetailProperty, nameof(Protocol.SubCaption));
            _protocolsList.ItemsSource = SensusServiceHelper.Get()?.RegisteredProtocols;
            _protocolsList.ItemTapped += async(o, e) =>
            {
                if (_protocolsList.SelectedItem == null)
                {
                    return;
                }

                Protocol selectedProtocol = _protocolsList.SelectedItem as Protocol;

                #region add actions
                List <string> actions = new List <string>();

                actions.Add(selectedProtocol.Running ? "Stop" : "Start");

                if (!string.IsNullOrWhiteSpace(selectedProtocol.ContactEmail))
                {
                    actions.Add("Email Study Manager for Help");
                }

                if (selectedProtocol.Running && selectedProtocol.AllowViewStatus)
                {
                    actions.Add("Status");
                }

                if (selectedProtocol.AllowViewData)
                {
                    actions.Add("View Data");
                }

                if (selectedProtocol.Running)
                {
                    if (selectedProtocol.AllowSubmitData)
                    {
                        actions.Add("Submit Data");
                    }

                    if (selectedProtocol.AllowParticipationScanning)
                    {
                        actions.Add("Display Participation");
                    }
                }

                if (selectedProtocol.AllowParticipationScanning && (selectedProtocol.RemoteDataStore?.CanRetrieveWrittenData ?? false))
                {
                    actions.Add("Scan Participation Barcode");
                }

                actions.Add("Edit");

                if (selectedProtocol.AllowCopy)
                {
                    actions.Add("Copy");
                }

                if (selectedProtocol.Shareable)
                {
                    actions.Add("Share");
                }

                List <Protocol> groupableProtocols = SensusServiceHelper.Get().RegisteredProtocols.Where(registeredProtocol => registeredProtocol != selectedProtocol && registeredProtocol.Groupable && registeredProtocol.GroupedProtocols.Count == 0).ToList();
                if (selectedProtocol.Groupable)
                {
                    if (selectedProtocol.GroupedProtocols.Count == 0 && groupableProtocols.Count > 0)
                    {
                        actions.Add("Group");
                    }
                    else if (selectedProtocol.GroupedProtocols.Count > 0)
                    {
                        actions.Add("Ungroup");
                    }
                }

                if (!selectedProtocol.Running && selectedProtocol.ScheduledStartCallback != null)
                {
                    actions.Remove("Start");
                    actions.Insert(0, "Cancel Scheduled Start");
                }

                actions.Add("Delete");
                #endregion

                #region process selected action
                string selectedAction = await DisplayActionSheet(selectedProtocol.Name, "Cancel", null, actions.ToArray());

                // must reset the protocol selection manually
                SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(() =>
                {
                    _protocolsList.SelectedItem = null;
                });

                if (selectedAction == "Status")
                {
                    List <Tuple <string, Dictionary <string, string> > > events = await selectedProtocol.TestHealthAsync(true);

                    await Navigation.PushAsync(new ViewTextLinesPage("Status", events.SelectMany(healthEventNameProperties =>
                    {
                        return(healthEventNameProperties.Item2.Select(propertyValue => healthEventNameProperties.Item1 + ":  " + propertyValue.Key + "=" + propertyValue.Value));
                    }).ToList(), null, null));
                }
                else if (selectedAction == "Start")
                {
                    selectedProtocol.StartWithUserAgreementAsync(null);
                }
                else if (selectedAction == "Cancel Scheduled Start")
                {
                    if (await DisplayAlert("Confirm Cancel", "Are you sure you want to cancel " + selectedProtocol.Name + "?", "Yes", "No"))
                    {
                        selectedProtocol.CancelScheduledStart();
                    }
                }
                else if (selectedAction == "Stop")
                {
                    if (await DisplayAlert("Confirm Stop", "Are you sure you want to stop " + selectedProtocol.Name + "?", "Yes", "No"))
                    {
                        await selectedProtocol.StopAsync();
                    }
                }
                else if (selectedAction == "Email Study Manager for Help")
                {
                    await SensusServiceHelper.Get().SendEmailAsync(selectedProtocol.ContactEmail, "Help with Sensus study:  " + selectedProtocol.Name,
                                                                   "Hello - " + Environment.NewLine +
                                                                   Environment.NewLine +
                                                                   "I am having trouble with a Sensus study. The name of the study is \"" + selectedProtocol.Name + "\"." + Environment.NewLine +
                                                                   Environment.NewLine +
                                                                   "Here is why I am sending this email:  ");
                }
                else if (selectedAction == "View Data")
                {
                    await Navigation.PushAsync(new ProbesViewPage(selectedProtocol));
                }
                else if (selectedAction == "Submit Data")
                {
                    try
                    {
                        if (await selectedProtocol.RemoteDataStore?.WriteLocalDataStoreAsync(CancellationToken.None))
                        {
                            await SensusServiceHelper.Get().FlashNotificationAsync("Data submitted.");
                        }
                        else
                        {
                            throw new Exception("Failed to submit data.");
                        }
                    }
                    catch (Exception ex)
                    {
                        await SensusServiceHelper.Get().FlashNotificationAsync("Error:  " + ex.Message);
                    }
                }
                else if (selectedAction == "Display Participation")
                {
                    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

                    // pop up wait screen while we submit the participation reward datum
                    SensusServiceHelper.Get().PromptForInputsAsync(
                        null,
                        new[] { new InputGroup {
                                    Name = "Please Wait", Inputs = { new LabelOnlyInput("Submitting participation information.", false) }
                                } },
                        cancellationTokenSource.Token,
                        false,
                        "Cancel",
                        null,
                        null,
                        null,
                        false,
                        async() =>
                    {
                        ParticipationRewardDatum participationRewardDatum = new ParticipationRewardDatum(DateTimeOffset.UtcNow, selectedProtocol.Participation);
                        participationRewardDatum.ProtocolId    = selectedProtocol.Id;
                        participationRewardDatum.ParticipantId = selectedProtocol.ParticipantId;

                        bool writeFailed = false;
                        try
                        {
                            await selectedProtocol.RemoteDataStore.WriteDatumAsync(participationRewardDatum, cancellationTokenSource.Token);
                        }
                        catch (Exception)
                        {
                            writeFailed = true;
                        }

                        if (writeFailed)
                        {
                            await SensusServiceHelper.Get().FlashNotificationAsync("Failed to submit participation information to remote server. You will not be able to verify your participation at this time.");
                        }

                        // cancel the token to close the input above, but only if the token hasn't already been canceled.
                        if (!cancellationTokenSource.IsCancellationRequested)
                        {
                            cancellationTokenSource.Cancel();
                        }

                        await SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(async() =>
                        {
                            // only show the QR code for the reward datum if the datum was written to the remote data store and if the data store can retrieve it.
                            await Navigation.PushAsync(new ParticipationReportPage(selectedProtocol, participationRewardDatum, !writeFailed && (selectedProtocol.RemoteDataStore?.CanRetrieveWrittenData ?? false)));
                        });
                    },
                        inputs =>
                    {
                        // if the prompt was closed by the user instead of the cancellation token, cancel the token in order
                        // to cancel the remote data store write. if the prompt was closed by the termination of the remote
                        // data store write (i.e., by the canceled token), then don't cancel the token again.
                        if (!cancellationTokenSource.IsCancellationRequested)
                        {
                            cancellationTokenSource.Cancel();
                        }
                    });
                }
                else if (selectedAction == "Scan Participation Barcode")
                {
                    try
                    {
                        string barcodeResult = await SensusServiceHelper.Get().ScanQrCodeAsync(QrCodePrefix.SENSUS_PARTICIPATION, Navigation);

                        if (barcodeResult == null)
                        {
                            return;
                        }

                        SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(() =>
                        {
                            CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

                            // pop up wait screen while we get the participation reward datum
                            SensusServiceHelper.Get().PromptForInputsAsync(
                                null,
                                new[] { new InputGroup {
                                            Name = "Please Wait", Inputs = { new LabelOnlyInput("Retrieving participation information.", false) }
                                        } },
                                cancellationTokenSource.Token,
                                false,
                                "Cancel",
                                null,
                                null,
                                null,
                                false,
                                async() =>
                            {
                                // after the page shows up, attempt to retrieve the participation reward datum.
                                try
                                {
                                    ParticipationRewardDatum participationRewardDatum = await selectedProtocol.RemoteDataStore.GetDatumAsync <ParticipationRewardDatum>(barcodeResult, cancellationTokenSource.Token);

                                    // cancel the token to close the input above, but only if the token hasn't already been canceled by the user.
                                    if (!cancellationTokenSource.IsCancellationRequested)
                                    {
                                        cancellationTokenSource.Cancel();
                                    }

                                    // ensure that the participation datum has not expired
                                    if (participationRewardDatum.Timestamp > DateTimeOffset.UtcNow.AddSeconds(-SensusServiceHelper.PARTICIPATION_VERIFICATION_TIMEOUT_SECONDS))
                                    {
                                        await SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(async() =>
                                        {
                                            await Navigation.PushAsync(new VerifiedParticipationPage(selectedProtocol, participationRewardDatum));
                                        });
                                    }
                                    else
                                    {
                                        await SensusServiceHelper.Get().FlashNotificationAsync("Participation barcode has expired. The participant needs to regenerate the barcode.");
                                    }
                                }
                                catch (Exception)
                                {
                                    await SensusServiceHelper.Get().FlashNotificationAsync("Failed to retrieve participation information.");
                                }
                                finally
                                {
                                    // cancel the token to close the input above, but only if the token hasn't already been canceled by the user. this will be
                                    // used if an exception is thrown while getting the participation reward datum.
                                    if (!cancellationTokenSource.IsCancellationRequested)
                                    {
                                        cancellationTokenSource.Cancel();
                                    }
                                }
                            },
                                inputs =>
                            {
                                // if the prompt was closed by the user instead of the cancellation token, cancel the token in order
                                // to cancel the datum retrieval. if the prompt was closed by the termination of the remote
                                // data store get (i.e., by the canceled token), then don't cancel the token again.
                                if (!cancellationTokenSource.IsCancellationRequested)
                                {
                                    cancellationTokenSource.Cancel();
                                }
                            });
                        });
                    }
                    catch (Exception ex)
                    {
                        string message = "Failed to scan barcode:  " + ex.Message;
                        SensusServiceHelper.Get().Logger.Log(message, LoggingLevel.Normal, GetType());
                        await SensusServiceHelper.Get().FlashNotificationAsync(message);
                    }
                }
                else if (selectedAction == "Edit")
                {
                    ExecuteActionUponProtocolAuthentication(selectedProtocol, () =>
                    {
                        SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(async() =>
                        {
                            ProtocolPage protocolPage = new ProtocolPage(selectedProtocol);
                            await Navigation.PushAsync(protocolPage);
                        });
                    });
                }
                else if (selectedAction == "Copy")
                {
                    // reset the protocol id, as we're creating a new study
                    selectedProtocol.CopyAsync(true, true);
                }
                else if (selectedAction == "Share")
                {
                    // make a deep copy of the selected protocol so we can reset it for sharing. don't reset the id of the protocol to keep
                    // it in the same study. also do not register the copy since we're just going to send it off.
                    selectedProtocol.CopyAsync(false, false, async selectedProtocolCopy =>
                    {
                        // write protocol to file and share
                        string sharePath = SensusServiceHelper.Get().GetSharePath(".json");
                        selectedProtocolCopy.Save(sharePath);
                        await SensusServiceHelper.Get().ShareFileAsync(sharePath, "Sensus Protocol:  " + selectedProtocolCopy.Name, "application/json");
                    });
                }
                else if (selectedAction == "Group")
                {
                    SensusServiceHelper.Get().PromptForInputAsync("Group",
                                                                  new ItemPickerPageInput("Select Protocols", groupableProtocols.Cast <object>().ToList(), "Name")
                    {
                        Multiselect = true
                    },
                                                                  null, true, "Group", null, null, null, false,
                                                                  input =>
                    {
                        if (input == null)
                        {
                            SensusServiceHelper.Get().FlashNotificationAsync("No protocols grouped.");
                            return;
                        }

                        ItemPickerPageInput itemPickerPageInput = input as ItemPickerPageInput;

                        List <Protocol> selectedProtocols = (itemPickerPageInput.Value as List <object>).Cast <Protocol>().ToList();

                        if (selectedProtocols.Count == 0)
                        {
                            SensusServiceHelper.Get().FlashNotificationAsync("No protocols grouped.");
                        }
                        else
                        {
                            selectedProtocol.GroupedProtocols.AddRange(selectedProtocols);
                            SensusServiceHelper.Get().FlashNotificationAsync("Grouped \"" + selectedProtocol.Name + "\" with " + selectedProtocols.Count + " other protocol" + (selectedProtocols.Count == 1 ? "" : "s") + ".");
                        }
                    });
                }
                else if (selectedAction == "Ungroup")
                {
                    if (await DisplayAlert("Ungroup " + selectedProtocol.Name + "?", "This protocol is currently grouped with the following other protocols:" + Environment.NewLine + Environment.NewLine + string.Concat(selectedProtocol.GroupedProtocols.Select(protocol => protocol.Name + Environment.NewLine)), "Ungroup", "Cancel"))
                    {
                        selectedProtocol.GroupedProtocols.Clear();
                    }
                }
                else if (selectedAction == "Delete")
                {
                    if (await DisplayAlert("Delete " + selectedProtocol.Name + "?", "This action cannot be undone.", "Delete", "Cancel"))
                    {
                        await selectedProtocol.DeleteAsync();
                    }
                }
            };
            #endregion

            Content = _protocolsList;

            #region add toolbar items
            ToolbarItems.Add(new ToolbarItem(null, "plus.png", async() =>
            {
                List <string> buttons = new string[] { "From QR Code", "From URL", "New" }.ToList();

                string action = await DisplayActionSheet("Add Study", "Back", null, buttons.ToArray());

                if (action == "From QR Code")
                {
                    string result = await SensusServiceHelper.Get().ScanQrCodeAsync(QrCodePrefix.SENSUS_PROTOCOL, Navigation);

                    if (result != null)
                    {
                        try
                        {
                            Protocol.DeserializeAsync(new Uri(result), Protocol.DisplayAndStartAsync);
                        }
                        catch (Exception ex)
                        {
                            await SensusServiceHelper.Get().FlashNotificationAsync("Failed to get study from QR code:  " + ex.Message);
                        }
                    }
                }
                else if (action == "From URL")
                {
                    SensusServiceHelper.Get().PromptForInputAsync(
                        "Download Protocol",
                        new SingleLineTextInput("Protocol URL:", Keyboard.Url),
                        null, true, null, null, null, null, false, input =>
                    {
                        // input might be null (user cancelled), or the value might be null (blank input submitted)
                        if (!string.IsNullOrEmpty(input?.Value?.ToString()))
                        {
                            Protocol.DeserializeAsync(new Uri(input.Value.ToString()), Protocol.DisplayAndStartAsync);
                        }
                    });
                }
                else if (action == "New")
                {
                    Protocol.Create("New Protocol");
                }
            }));

            ToolbarItems.Add(new ToolbarItem("ID", null, async() =>
            {
                await DisplayAlert("Device ID", SensusServiceHelper.Get().DeviceId, "Close");
            }, ToolbarItemOrder.Secondary));

            ToolbarItems.Add(new ToolbarItem("Log", null, async() =>
            {
                await Navigation.PushAsync(new ViewTextLinesPage("Log", SensusServiceHelper.Get().Logger.Read(200, true),

                                                                 async() =>
                {
                    string sharePath = null;
                    try
                    {
                        sharePath = SensusServiceHelper.Get().GetSharePath(".txt");
                        SensusServiceHelper.Get().Logger.CopyTo(sharePath);
                    }
                    catch (Exception)
                    {
                        sharePath = null;
                    }

                    if (sharePath != null)
                    {
                        await SensusServiceHelper.Get().ShareFileAsync(sharePath, "Log:  " + Path.GetFileName(sharePath), "text/plain");
                    }
                },

                                                                 () => SensusServiceHelper.Get().Logger.Clear()));
            }, ToolbarItemOrder.Secondary));

#if __ANDROID__
            ToolbarItems.Add(new ToolbarItem("Stop", null, async() =>
            {
                if (await DisplayAlert("Confirm", "Are you sure you want to stop Sensus? This will end your participation in all studies.", "Stop Sensus", "Go Back"))
                {
                    SensusServiceHelper.Get().StopProtocols();

                    (SensusServiceHelper.Get() as Android.IAndroidSensusServiceHelper)?.StopAndroidSensusService();
                }
            }, ToolbarItemOrder.Secondary));
#endif

            ToolbarItems.Add(new ToolbarItem("About", null, async() =>
            {
                await DisplayAlert("About Sensus", "Version:  " + SensusServiceHelper.Get().Version, "OK");
            }, ToolbarItemOrder.Secondary));
            #endregion
        }
Пример #19
0
        public async void RemoveListener(EventHandler <PositionEventArgs> listener)
        {
            if (ListeningForChanges)
            {
                await _locator.StopListeningAsync();
            }

            PositionChanged -= listener;

            _listenerHeadings.RemoveAll(t => t.Item1 == listener);

            if (ListeningForChanges)
            {
                await _locator.StartListeningAsync(TimeSpan.FromMilliseconds(SensusServiceHelper.Get().GpsMinTimeDelayMS), SensusServiceHelper.Get().GpsMinDistanceDelayMeters, _listenerHeadings.Any(t => t.Item2), GetListenerSettings());
            }
            else
            {
                SensusServiceHelper.Get().Logger.Log("All listeners removed from GPS receiver. Stopped listening.", LoggingLevel.Normal, GetType());
            }
        }
Пример #20
0
        public async Task StartAsync()
        {
            lock (_stateLocker)
            {
                // don't attempt to start the probe if it is not enabled. this can happen, e.g., when remote protocol
                // updates disable the probe and the probe is subsequently restarted to take on the update values.
                if (Enabled && _state == ProbeState.Stopped)
                {
                    _state = ProbeState.Initializing;
                }
                else
                {
                    SensusServiceHelper.Get().Logger.Log("Attempted to start probe, but it was already in state " + _state + " with " + nameof(Enabled) + "=" + Enabled + ".", LoggingLevel.Normal, GetType());
                    return;
                }
            }

            try
            {
                await InitializeAsync();

                lock (_stateLocker)
                {
                    _state = ProbeState.Starting;
                }

                // start data rate calculators
                _rawRateCalculator.Start();
                _storageRateCalculator.Start();
                _uiUpdateRateCalculator.Start();

                await ProtectedStartAsync();

                lock (_stateLocker)
                {
                    _state = ProbeState.Running;
                }

                lock (_startStopTimes)
                {
                    _startStopTimes.Add(new Tuple <bool, DateTime>(true, DateTime.Now));
                    _startStopTimes.RemoveAll(t => t.Item2 < Protocol.ParticipationHorizon);
                }
            }
            catch (Exception startException)
            {
                if (startException is NotSupportedException)
                {
                    Enabled = false;
                }

                // stop probe to clean up any inconsistent state information
                try
                {
                    await StopAsync();
                }
                catch (Exception stopException)
                {
                    SensusServiceHelper.Get().Logger.Log("Failed to stop probe after failing to start it:  " + stopException.Message, LoggingLevel.Normal, GetType());
                }

                string message = "Sensus failed to start probe \"" + GetType().Name + "\":  " + startException.Message;
                SensusServiceHelper.Get().Logger.Log(message, LoggingLevel.Normal, GetType());
                await SensusServiceHelper.Get().FlashNotificationAsync(message);

                throw startException;
            }
        }
Пример #21
0
        public async void AddListener(EventHandler <PositionEventArgs> listener, bool includeHeading)
        {
            if (SensusServiceHelper.Get().ObtainPermission(Permission.Location) != PermissionStatus.Granted)
            {
                throw new Exception("Could not access GPS.");
            }

            // if we're already listening, stop listening first so that the locator can be configured with
            // the most recent listening settings below.
            if (ListeningForChanges)
            {
                await _locator.StopListeningAsync();
            }

            // add new listener
            PositionChanged += listener;
            _listenerHeadings.Add(new Tuple <EventHandler <PositionEventArgs>, bool>(listener, includeHeading));

            _locator.DesiredAccuracy = SensusServiceHelper.Get().GpsDesiredAccuracyMeters;

            await _locator.StartListeningAsync(TimeSpan.FromMilliseconds(SensusServiceHelper.Get().GpsMinTimeDelayMS), SensusServiceHelper.Get().GpsMinDistanceDelayMeters, _listenerHeadings.Any(t => t.Item2), GetListenerSettings());

            SensusServiceHelper.Get().Logger.Log("GPS receiver is now listening for changes.", LoggingLevel.Normal, GetType());
        }
Пример #22
0
        /// <summary>
        /// Stores a <see cref="Datum"/> within the <see cref="LocalDataStore"/>. Will not throw an <see cref="Exception"/>.
        /// </summary>
        /// <param name="datum">Datum.</param>
        /// <param name="cancellationToken">Cancellation token.</param>
        public async Task StoreDatumAsync(Datum datum, CancellationToken?cancellationToken = null)
        {
            // it's possible for the current method to be called when the protocol is not running. the obvious case is when
            // the protocol is paused, but there are other race-like conditions. we try to prevent this (e.g., by forcing
            // the user to start the protocol before taking a survey saved from a previous run of the app), but there are
            // probably corner cases we haven't accounted for. at the very least, there are race conditions (e.g., taking a
            // survey when a protocol is about to stop) that could cause data to be stored without a running protocol.
            if (_protocol.State != ProtocolState.Running)
            {
                return;
            }

            // track/limit the raw rate of non-null data. all null data will pass this test, and this is
            // fine given such data are generated by polling probes when no data were retrieved. such
            // return values from polling probes are used to indicate that the poll was completed, which
            // will be reflected in the _mostRecentStoreTimestamp below.
            if (datum != null)
            {
                // impose a limit on the raw data rate
                if (_rawRateCalculator.Add(datum) == DataRateCalculator.SamplingAction.Drop)
                {
                    return;
                }

                // set properties that we were unable to set within the datum constructor.
                datum.ProtocolId    = Protocol.Id;
                datum.ParticipantId = Protocol.ParticipantId;

                // tag the data if we're in tagging mode, indicated with a non-null event id on the protocol. avoid
                // any race conditions related to starting/stopping a tagging by getting the required values and
                // then checking both for validity. we need to guarantee that any tagged datum has both an id and tags.
                string        taggedEventId   = Protocol.TaggedEventId;
                List <string> taggedEventTags = Protocol.TaggedEventTags.ToList();
                if (!string.IsNullOrWhiteSpace(taggedEventId) && taggedEventTags.Count > 0)
                {
                    datum.TaggedEventId   = taggedEventId;
                    datum.TaggedEventTags = taggedEventTags;
                }

                // if the protocol is configured with a sensing agent,
                if (Protocol.Agent != null)
                {
                    datum.SensingAgentStateDescription = Protocol.Agent.StateDescription;
                }
            }

            // store non-null data
            if (_storeData && datum != null)
            {
                #region update chart data
                ChartDataPoint chartDataPoint = null;

                try
                {
                    chartDataPoint = GetChartDataPointFromDatum(datum);
                }
                catch (NotImplementedException)
                {
                }

                if (chartDataPoint != null)
                {
                    lock (_chartData)
                    {
                        _chartData.Add(chartDataPoint);

                        while (_chartData.Count > 0 && _chartData.Count > _maxChartDataCount)
                        {
                            _chartData.RemoveAt(0);
                        }
                    }
                }
                #endregion

                // write datum to local data store. catch any exceptions, as the caller (e.g., a listening
                // probe) could very well be unprotected on the UI thread. throwing an exception here can crash the app.
                try
                {
                    _protocol.LocalDataStore.WriteDatum(datum, cancellationToken.GetValueOrDefault());

                    // track the storage rate
                    _storageRateCalculator.Add(datum);
                }
                catch (Exception ex)
                {
                    SensusServiceHelper.Get().Logger.Log("Failed to write datum:  " + ex, LoggingLevel.Normal, GetType());
                }
            }

            // update the timestamp of the most recent store. this is used to calculate storage latency, so we
            // do not restrict its values to those obtained when non-null data are stored (see above). some
            // probes call this method with null data to signal that they have run their collection to completion.
            _mostRecentStoreTimestamp = DateTimeOffset.UtcNow;

            // don't update the UI too often, as doing so at really high rates causes UI deadlocks. always let
            // null data update the UI, as these are only generated by polling probes at low rates.
            if (datum == null || _uiUpdateRateCalculator.Add(datum) == DataRateCalculator.SamplingAction.Keep)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SubCaption)));
            }

            // track the most recent datum regardless of whether the datum is null or whether we're storing data
            Datum previousDatum = _mostRecentDatum;
            _mostRecentDatum = datum;

            // notify observers of the stored data and associated UI values
            await(MostRecentDatumChanged?.Invoke(previousDatum, _mostRecentDatum) ?? Task.CompletedTask);

            // let the script probe's agent observe the data, as long as the probe is enabled and there is an agent.
            Protocol.TryGetProbe(typeof(ScriptProbe), out Probe scriptProbe);
            if (scriptProbe?.Enabled ?? false)
            {
                // agents might be third-party and badly behaving...catch their exceptions.
                try
                {
                    await((scriptProbe as ScriptProbe).Agent?.ObserveAsync(datum) ?? Task.CompletedTask);
                }
                catch (Exception ex)
                {
                    SensusServiceHelper.Get().Logger.Log("Exception while script probe agent was observing datum:  " + ex.Message, LoggingLevel.Normal, GetType());
                }
            }

            // let the protocol's sensing agent observe the data, and schedule any returned control
            // completion check. agents might be third-party and badly behaving...catch their exceptions.
            try
            {
                await Protocol.ScheduleAgentControlCompletionCheckAsync(await (Protocol.Agent?.ObserveAsync(datum, cancellationToken.GetValueOrDefault()) ?? Task.FromResult <ControlCompletionCheck>(null)));
            }
            catch (Exception ex)
            {
                SensusServiceHelper.Get().Logger.Log("Exception while sensing agent was observing datum:  " + ex.Message, LoggingLevel.Normal, GetType());
            }
        }
Пример #23
0
        /// <summary>
        /// Initializes a new instance of the <see cref="ProbePage"/> class.
        /// </summary>
        /// <param name="probe">Probe to display.</param>
        public ProbePage(Probe probe)
        {
            Title = "Probe";

            StackLayout contentLayout = new StackLayout
            {
                Orientation     = StackOrientation.Vertical,
                VerticalOptions = LayoutOptions.FillAndExpand
            };

            string type = "";

            if (probe is ListeningProbe)
            {
                type = "Listening";
            }
            else if (probe is PollingProbe)
            {
                type = "Polling";
            }

            contentLayout.Children.Add(new ContentView
            {
                Content = new Label
                {
                    Text              = probe.DisplayName + (type == "" ? "" : " (" + type + ")"),
                    FontSize          = 20,
                    FontAttributes    = FontAttributes.Italic,
                    TextColor         = Color.Accent,
                    HorizontalOptions = LayoutOptions.Center
                },
                Padding = new Thickness(0, 10, 0, 10)
            });

            foreach (StackLayout stack in UiProperty.GetPropertyStacks(probe))
            {
                contentLayout.Children.Add(stack);
            }

            #region script probes
            if (probe is ScriptProbe)
            {
                ScriptProbe scriptProbe = probe as ScriptProbe;

                Button editScriptsButton = new Button
                {
                    Text     = "Edit Scripts",
                    FontSize = 20
                };

                contentLayout.Children.Add(editScriptsButton);

                editScriptsButton.Clicked += async(o, e) =>
                {
                    await Navigation.PushAsync(new ScriptRunnersPage(scriptProbe));
                };

                Button shareScriptButton = new Button
                {
                    Text     = "Share Definition",
                    FontSize = 20
                };

                contentLayout.Children.Add(shareScriptButton);

                shareScriptButton.Clicked += async(o, e) =>
                {
                    string sharePath = SensusServiceHelper.Get().GetSharePath(".json");

                    using (StreamWriter shareFile = new StreamWriter(sharePath))
                    {
                        shareFile.WriteLine(JsonConvert.SerializeObject(probe, SensusServiceHelper.JSON_SERIALIZER_SETTINGS));
                    }

                    await SensusServiceHelper.Get().ShareFileAsync(sharePath, "Probe Definition", "application/json");
                };
            }
            #endregion

            #region proximity probe
            if (probe is IPointsOfInterestProximityProbe)
            {
                Button editTriggersButton = new Button
                {
                    Text              = "Edit Triggers",
                    FontSize          = 20,
                    HorizontalOptions = LayoutOptions.FillAndExpand
                };

                contentLayout.Children.Add(editTriggersButton);

                editTriggersButton.Clicked += async(o, e) =>
                {
                    await Navigation.PushAsync(new ProximityTriggersPage(probe as IPointsOfInterestProximityProbe));
                };
            }
            #endregion

            #region estimote probe
            if (probe is EstimoteBeaconProbe)
            {
                Button editBeaconsButton = new Button
                {
                    Text              = "Edit Beacons",
                    FontSize          = 20,
                    HorizontalOptions = LayoutOptions.FillAndExpand
                };

                contentLayout.Children.Add(editBeaconsButton);

                editBeaconsButton.Clicked += async(sender, e) =>
                {
                    await Navigation.PushAsync(new EstimoteBeaconProbeBeaconsPage(probe as EstimoteBeaconProbe));
                };
            }
            #endregion

            #region anonymization
            List <PropertyInfo> anonymizableProperties = probe.DatumType.GetProperties().Where(property => property.GetCustomAttribute <Anonymizable>() != null).ToList();

            if (anonymizableProperties.Count > 0)
            {
                contentLayout.Children.Add(new Label
                {
                    Text              = "Anonymization",
                    FontSize          = 20,
                    FontAttributes    = FontAttributes.Italic,
                    TextColor         = Color.Accent,
                    HorizontalOptions = LayoutOptions.Center
                });

                List <StackLayout> anonymizablePropertyStacks = new List <StackLayout>();

                foreach (PropertyInfo anonymizableProperty in anonymizableProperties)
                {
                    Anonymizable anonymizableAttribute = anonymizableProperty.GetCustomAttribute <Anonymizable>(true);

                    Label propertyLabel = new Label
                    {
                        Text              = anonymizableAttribute.PropertyDisplayName ?? anonymizableProperty.Name + ":",
                        FontSize          = 20,
                        HorizontalOptions = LayoutOptions.Start
                    };

                    // populate a picker of anonymizers for the current property
                    Picker anonymizerPicker = new Picker
                    {
                        Title             = "Select Anonymizer",
                        HorizontalOptions = LayoutOptions.FillAndExpand
                    };

                    anonymizerPicker.Items.Add("Do Not Anonymize");
                    foreach (Anonymizer anonymizer in anonymizableAttribute.AvailableAnonymizers)
                    {
                        anonymizerPicker.Items.Add(anonymizer.DisplayText);
                    }

                    anonymizerPicker.SelectedIndexChanged += (o, e) =>
                    {
                        Anonymizer selectedAnonymizer = null;
                        if (anonymizerPicker.SelectedIndex > 0)
                        {
                            selectedAnonymizer = anonymizableAttribute.AvailableAnonymizers[anonymizerPicker.SelectedIndex - 1];  // subtract one from the selected index since the JsonAnonymizer's collection of anonymizers start after the "None" option within the picker.
                        }

                        probe.Protocol.JsonAnonymizer.SetAnonymizer(anonymizableProperty, selectedAnonymizer);
                    };

                    // set the picker's index to the current anonymizer (or "Do Not Anonymize" if there is no current)
                    Anonymizer currentAnonymizer = probe.Protocol.JsonAnonymizer.GetAnonymizer(anonymizableProperty);
                    int        currentIndex      = 0;
                    if (currentAnonymizer != null)
                    {
                        currentIndex = anonymizableAttribute.AvailableAnonymizers.IndexOf(currentAnonymizer) + 1;
                    }

                    anonymizerPicker.SelectedIndex = currentIndex;

                    StackLayout anonymizablePropertyStack = new StackLayout
                    {
                        Orientation       = StackOrientation.Horizontal,
                        HorizontalOptions = LayoutOptions.FillAndExpand,
                        Children          = { propertyLabel, anonymizerPicker }
                    };

                    anonymizablePropertyStacks.Add(anonymizablePropertyStack);
                }

                foreach (StackLayout anonymizablePropertyStack in anonymizablePropertyStacks.OrderBy(s => (s.Children[0] as Label).Text))
                {
                    contentLayout.Children.Add(anonymizablePropertyStack);
                }
            }
            #endregion

            Content = new ScrollView
            {
                Content = contentLayout
            };
        }
Пример #24
0
 protected virtual Task ProtectedStopAsync()
 {
     SensusServiceHelper.Get().Logger.Log("Stopping...", LoggingLevel.Normal, GetType());
     return(Task.CompletedTask);
 }
Пример #25
0
        /// <summary>
        /// Initializes a new instance of the <see cref="PointsOfInterestPage"/> class.
        /// </summary>
        /// <param name="pointsOfInterest">Points of interest to display.</param>
        public PointsOfInterestPage(ConcurrentObservableCollection <PointOfInterest> pointsOfInterest)
        {
            Title = "Points of Interest";

            ListView pointsOfInterestList = new ListView(ListViewCachingStrategy.RecycleElement);

            pointsOfInterestList.ItemTemplate = new DataTemplate(typeof(TextCell));
            pointsOfInterestList.ItemTemplate.SetBinding(TextCell.TextProperty, new Binding(".", stringFormat: "{0}"));
            pointsOfInterestList.ItemsSource = pointsOfInterest;
            pointsOfInterestList.ItemTapped += async(o, e) =>
            {
                if (pointsOfInterestList.SelectedItem == null)
                {
                    return;
                }

                PointOfInterest selectedPointOfInterest = pointsOfInterestList.SelectedItem as PointOfInterest;

                string selectedAction = await DisplayActionSheet(selectedPointOfInterest.ToString(), "Cancel", null, "Delete");

                if (selectedAction == "Delete")
                {
                    if (await DisplayAlert("Delete " + selectedPointOfInterest.Name + "?", "This action cannot be undone.", "Delete", "Cancel"))
                    {
                        pointsOfInterest.Remove(selectedPointOfInterest);
                        pointsOfInterestList.SelectedItem = null;  // reset it manually, since it isn't done automatically.
                    }
                }
            };

            Content = pointsOfInterestList;

            CancellationTokenSource gpsCancellationTokenSource = null;

            ToolbarItems.Add(new ToolbarItem(null, "plus.png", async() =>
            {
                List <Input> inputs = await SensusServiceHelper.Get().PromptForInputsAsync("Define Point Of Interest", new Input[]
                {
                    new SingleLineTextInput("POI Name:", Keyboard.Text)
                    {
                        Required = false
                    },
                    new SingleLineTextInput("POI Type:", Keyboard.Text)
                    {
                        Required = false
                    },
                    new SingleLineTextInput("Address:", Keyboard.Text)
                    {
                        Required = false
                    }
                }, null, true, null, null, null, null, false);

                if (inputs == null)
                {
                    return;
                }

                string name    = inputs[0].Value as string;
                string type    = inputs[1].Value as string;
                string address = inputs[2].Value as string;

                if (string.IsNullOrWhiteSpace(name) && string.IsNullOrWhiteSpace(type))
                {
                    await SensusServiceHelper.Get().FlashNotificationAsync("You must enter either a name or type (or both).");
                }
                else
                {
                    Action <List <Position> > addPOI = new Action <List <Position> >(poiPositions =>
                    {
                        SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(async() =>
                        {
                            if (poiPositions != null && poiPositions.Count > 0 && await DisplayAlert("Add POI?", "Would you like to add " + poiPositions.Count + " point(s) of interest?", "Yes", "No"))
                            {
                                foreach (Position poiPosition in poiPositions)
                                {
                                    pointsOfInterest.Add(new PointOfInterest(name, type, poiPosition.ToGeolocationPosition()));
                                }
                            }
                        });
                    });

                    string newPinName = name + (string.IsNullOrWhiteSpace(type) ? "" : " (" + type + ")");

                    if (string.IsNullOrWhiteSpace(address))
                    {
                        // cancel existing token source if we have one
                        if (gpsCancellationTokenSource != null && !gpsCancellationTokenSource.IsCancellationRequested)
                        {
                            gpsCancellationTokenSource.Cancel();
                        }

                        gpsCancellationTokenSource = new CancellationTokenSource();

                        Plugin.Geolocator.Abstractions.Position gpsPosition = await GpsReceiver.Get().GetReadingAsync(gpsCancellationTokenSource.Token, true);

                        if (gpsPosition != null)
                        {
                            SensusServiceHelper.Get().GetPositionsFromMapAsync(gpsPosition.ToFormsPosition(), newPinName, addPOI);
                        }
                    }
                    else
                    {
                        SensusServiceHelper.Get().GetPositionsFromMapAsync(address, newPinName, addPOI);
                    }
                }
            }));
        }
Пример #26
0
 public override void DidFailToUpdatePosition(EILIndoorLocationManager locationManager, NSError error)
 {
     SensusServiceHelper.Get().Logger.Log("Failed to update indoor location position:  " + error, LoggingLevel.Normal, GetType());
 }
Пример #27
0
        public TaggingPage(Protocol protocol)
        {
            Title = "Tagging";

            Button selectTagsButton = new Button
            {
                Text              = GetSelectTagButtonText(protocol),
                FontSize          = 20,
                HorizontalOptions = LayoutOptions.FillAndExpand
            };

            selectTagsButton.Clicked += async(sender, e) =>
            {
                List <object> availableTags = protocol.AvailableTags.Cast <object>().ToList();
                availableTags.Sort();

                ItemPickerPageInput tagPickerInput = await SensusServiceHelper.Get().PromptForInputAsync("Select Tag(s)",
                                                                                                         new ItemPickerPageInput("Available Tags", availableTags)
                {
                    Multiselect = true,
                    Required    = true
                },
                                                                                                         null, true, "OK", null, null, null, false) as ItemPickerPageInput;

                if (tagPickerInput != null)
                {
                    protocol.TaggedEventTags = (tagPickerInput.Value as List <object>).Cast <string>().ToList();
                    protocol.TaggedEventTags.Sort();
                    selectTagsButton.Text = GetSelectTagButtonText(protocol);
                }
            };

            Button addTagButton = new Button
            {
                Text              = "Add Tag",
                FontSize          = 20,
                HorizontalOptions = LayoutOptions.FillAndExpand
            };

            addTagButton.Clicked += async(sender, e) =>
            {
                SingleLineTextInput tagInput = await SensusServiceHelper.Get().PromptForInputAsync("Add Tag",
                                                                                                   new SingleLineTextInput("Tag:", Keyboard.Text)
                {
                    Required = true
                },
                                                                                                   null, true, "Add", null, null, null, false) as SingleLineTextInput;

                string tagToAdd = tagInput?.Value?.ToString().Trim();

                if (!string.IsNullOrWhiteSpace(tagToAdd) && !protocol.AvailableTags.Contains(tagToAdd))
                {
                    protocol.AvailableTags.Add(tagToAdd);
                }
            };

            Button startStopButton = new Button
            {
                Text              = protocol.TaggedEventId == null ? "Start" : "Stop",
                FontSize          = 50,
                HorizontalOptions = LayoutOptions.FillAndExpand,
                VerticalOptions   = LayoutOptions.FillAndExpand
            };

            Button exportTaggingsButton = new Button
            {
                Text              = "Export Taggings",
                FontSize          = 20,
                HorizontalOptions = LayoutOptions.FillAndExpand
            };

            exportTaggingsButton.Clicked += async(sender, e) =>
            {
                try
                {
                    if (protocol.TaggingsToExport.Count == 0)
                    {
                        await SensusServiceHelper.Get().FlashNotificationAsync("There are no taggings to export. Select tags and tap Start to generate a tagging.");
                    }
                    else
                    {
                        string path = SensusServiceHelper.Get().GetSharePath(".csv");
                        File.WriteAllText(path, "tagged-event-id,start,end,tags" + Environment.NewLine + string.Concat(protocol.TaggingsToExport.Select(tagging => tagging + Environment.NewLine)));
                        await SensusServiceHelper.Get().ShareFileAsync(path, protocol.Name + ":  Taggings", "text/plain");
                    }
                }
                catch (Exception ex)
                {
                    await SensusServiceHelper.Get().FlashNotificationAsync("Failed to export taggings:  " + ex.Message);
                }
            };

            startStopButton.Clicked += async(sender, e) =>
            {
                // start tagging
                if (startStopButton.Text == "Start")
                {
                    if (protocol.TaggedEventTags.Count > 0)
                    {
                        // mark the start of the tagging
                        protocol.TaggingStartTimestamp = DateTimeOffset.UtcNow;

                        // set the tagged event id, which signals the data storage routines to apply tags to all data.
                        protocol.TaggedEventId = Guid.NewGuid().ToString();

                        startStopButton.Text       = "Stop";
                        selectTagsButton.IsEnabled = addTagButton.IsEnabled = exportTaggingsButton.IsEnabled = false;
                    }
                    else
                    {
                        await SensusServiceHelper.Get().FlashNotificationAsync("Must select 1 or more tags before starting.");
                    }
                }
                // stop tagging
                else
                {
                    // hang on to the id, as we're about to clear it.
                    string taggedEventId = protocol.TaggedEventId;

                    // clear the tagged event id, which signals the data storage routines to stop applying tags to data.
                    protocol.TaggedEventId = null;

                    // mark the end of the tagging
                    protocol.TaggingEndTimestamp = DateTimeOffset.UtcNow;

                    if (await DisplayAlert("Confirm Tagging", "Would you like to keep the following tagging?" + Environment.NewLine + Environment.NewLine +
                                           "Start:  " + protocol.TaggingStartTimestamp + Environment.NewLine +
                                           "End:  " + protocol.TaggingEndTimestamp + Environment.NewLine +
                                           "Tags:  " + string.Concat(protocol.TaggedEventTags.Select(tag => tag + ", ")).Trim(',', ' '), "Yes", "No"))
                    {
                        protocol.TaggingsToExport.Add(taggedEventId + "," + protocol.TaggingStartTimestamp + "," + protocol.TaggingEndTimestamp + "," + string.Concat(protocol.TaggedEventTags.Select(tag => tag + "|")).Trim('|'));
                        await SensusServiceHelper.Get().FlashNotificationAsync("Tagging kept. Tap the export button when finished with all taggings.");
                    }
                    else
                    {
                        await SensusServiceHelper.Get().FlashNotificationAsync("Tagging discarded.");
                    }

                    // reset start and end. don't reset the currently set tags, as the user likely wishes to use them again.
                    protocol.TaggingStartTimestamp = protocol.TaggingEndTimestamp = null;

                    startStopButton.Text       = "Start";
                    selectTagsButton.IsEnabled = addTagButton.IsEnabled = exportTaggingsButton.IsEnabled = true;
                }
            };

            // a tagging might have been started on a previous display of this page. enabled buttons if a tagging is not ongoing.
            selectTagsButton.IsEnabled = addTagButton.IsEnabled = exportTaggingsButton.IsEnabled = protocol.TaggedEventId == null;

            Content = new ScrollView
            {
                Content = new StackLayout
                {
                    Orientation       = StackOrientation.Vertical,
                    HorizontalOptions = LayoutOptions.FillAndExpand,
                    Children          =
                    {
                        selectTagsButton,
                        addTagButton,
                        startStopButton,
                        exportTaggingsButton
                    }
                }
            };
        }
Пример #28
0
        private void OpenIntentAsync(Intent intent)
        {
            new Thread(() =>
            {
                // wait for service helper to be initialized, since this method might be called before the service starts up
                // and initializes the service helper.
                int timeToWaitMS   = 60000;
                int waitIntervalMS = 1000;
                while (SensusServiceHelper.Get() == null && timeToWaitMS > 0)
                {
                    Thread.Sleep(waitIntervalMS);
                    timeToWaitMS -= waitIntervalMS;
                }

                if (SensusServiceHelper.Get() == null)
                {
                    // don't use SensusServiceHelper.Get().FlashNotificationAsync because service helper is null
                    RunOnUiThread(() =>
                    {
                        Toast.MakeText(this, "Failed to get service helper. Cannot open Intent.", ToastLength.Long);
                    });

                    return;
                }

                // open page to view protocol if a protocol was passed to us
                if (intent.Data != null)
                {
                    global::Android.Net.Uri dataURI = intent.Data;

                    try
                    {
                        if (intent.Scheme == "http" || intent.Scheme == "https")
                        {
                            Protocol.DeserializeAsync(new Uri(dataURI.ToString()), Protocol.DisplayAndStartAsync);
                        }
                        else if (intent.Scheme == "content" || intent.Scheme == "file")
                        {
                            byte[] bytes = null;

                            try
                            {
                                MemoryStream memoryStream = new MemoryStream();
                                Stream inputStream        = ContentResolver.OpenInputStream(dataURI);
                                inputStream.CopyTo(memoryStream);
                                inputStream.Close();
                                bytes = memoryStream.ToArray();
                            }
                            catch (Exception ex)
                            {
                                SensusServiceHelper.Get().Logger.Log("Failed to read bytes from local file URI \"" + dataURI + "\":  " + ex.Message, LoggingLevel.Normal, GetType());
                            }

                            if (bytes != null)
                            {
                                Protocol.DeserializeAsync(bytes, Protocol.DisplayAndStartAsync);
                            }
                        }
                        else
                        {
                            SensusServiceHelper.Get().Logger.Log("Sensus didn't know what to do with URI \"" + dataURI + "\".", LoggingLevel.Normal, GetType());
                        }
                    }
                    catch (Exception ex)
                    {
                        SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(() =>
                        {
                            new AlertDialog.Builder(this).SetTitle("Failed to get protocol").SetMessage(ex.Message).Show();
                        });
                    }
                }
            }).Start();
        }
Пример #29
0
        protected override async void ProbeTappedAsync(object sender, ItemTappedEventArgs e)
        {
            Probe probe = e.Item as Probe;

            if (probe is EstimoteBeaconProbe)
            {
                EstimoteIndoorLocationView locationView;

#if __ANDROID__
                locationView = new EstimoteIndoorLocationView(global::Android.App.Application.Context);
#elif __IOS__
                locationView = new EstimoteIndoorLocationView
                {
                    PositionImage = UIKit.UIImage.FromFile("account.png")
                };
#endif

                ContentPage indoorLocationPage = new ContentPage
                {
                    Title   = "Estimote Indoor Location",
                    Content = locationView.ToView()
                };

                await Navigation.PushAsync(indoorLocationPage);

                probe.MostRecentDatumChanged += (previous, current) =>
                {
                    SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(() =>
                    {
                        // indoor positioning will report null if the user is outside the area
                        if (current == null)
                        {
                            indoorLocationPage.Content.IsVisible = false;
                        }
                        else
                        {
                            indoorLocationPage.Content.IsVisible = true;

                            EstimoteIndoorLocationDatum currentEstimoteDatum = current as EstimoteIndoorLocationDatum;

                            // draw location before updating position
#if __ANDROID__
                            locationView.SetLocation(currentEstimoteDatum.EstimoteLocation);
#elif __IOS__
                            locationView.DrawLocation(currentEstimoteDatum.EstimoteLocation);
#endif

                            locationView.UpdatePosition(currentEstimoteDatum.EstimotePosition);
                        }
                    });

                    return(Task.CompletedTask);
                };
            }
            else
            {
                SfChart chart = null;

                try
                {
                    chart = probe.GetChart();
                }
                catch (NotImplementedException)
                {
                }

                if (chart == null)
                {
                    await SensusServiceHelper.Get().FlashNotificationAsync("Charts are not available for " + probe.DisplayName + " data.");
                }
                else
                {
                    ContentPage chartPage = new ContentPage
                    {
                        Title   = probe.DisplayName,
                        Content = chart,
                    };

                    chartPage.ToolbarItems.Add(new ToolbarItem("Refresh", null, () =>
                    {
                        chartPage.Content = probe.GetChart();
                    }));

                    chartPage.ToolbarItems.Add(new ToolbarItem("+", null, async() =>
                    {
                        if (probe.MaxChartDataCount < 200)
                        {
                            probe.MaxChartDataCount += 10;
                        }

                        await FlashChartDataCountAsync(probe);
                    }));

                    chartPage.ToolbarItems.Add(new ToolbarItem("-", null, async() =>
                    {
                        if (probe.MaxChartDataCount > 10)
                        {
                            probe.MaxChartDataCount -= 10;
                        }

                        await FlashChartDataCountAsync(probe);
                    }));

                    await Navigation.PushAsync(chartPage);
                }
            }
        }
Пример #30
0
        public PendingScriptsPage()
        {
            Title = "Pending Surveys";

            SensusServiceHelper.Get().RemoveExpiredScripts(true);

            ListView scriptList = new ListView();

            scriptList.ItemTemplate = new DataTemplate(typeof(TextCell));
            scriptList.ItemTemplate.SetBinding(TextCell.TextProperty, new Binding(".", converter: new ScriptTextConverter()));
            scriptList.ItemTemplate.SetBinding(TextCell.DetailProperty, new Binding(".", converter: new ScriptDetailConverter()));
            scriptList.ItemsSource = SensusServiceHelper.Get().ScriptsToRun;
            scriptList.ItemTapped += (o, e) =>
            {
                if (scriptList.SelectedItem == null)
                {
                    return;
                }

                Script script = scriptList.SelectedItem as Script;

                // reset list selection
                scriptList.SelectedItem = null;

                SensusServiceHelper.Get().PromptForInputsAsync(script.RunTime, script.InputGroups, null, script.Runner.AllowCancel, null, null, "You have not completed all required fields. You will not receive credit for your responses if you continue. Do you want to continue?", "Are you ready to submit your responses?", script.Runner.DisplayProgress, null, async inputGroups =>
                {
                    bool canceled = inputGroups == null;

                    // process all inputs in the script
                    foreach (InputGroup inputGroup in script.InputGroups)
                    {
                        foreach (Input input in inputGroup.Inputs)
                        {
                            // only consider inputs that still need to be stored. if an input has already been stored, it should be ignored.
                            if (input.NeedsToBeStored)
                            {
                                // if the user canceled the prompts, reset the input. we reset here within the above if-check because if an
                                // input has already been stored we should not reset it. its value and read-only status are fixed for all
                                // time, even if the prompts are later redisplayed by the invalid script handler.
                                if (canceled)
                                {
                                    input.Reset();
                                }
                                else if (input.Valid && input.Display)  // store all inputs that are valid and displayed. some might be valid from previous responses but not displayed because the user navigated back through the survey and changed a previous response that caused a subsesequently displayed input to be hidden via display contingencies.
                                {
                                    // the _script.Id allows us to link the data to the script that the user created. it never changes. on the other hand, the script
                                    // that is passed into this method is always a copy of the user-created script. the script.Id allows us to link the various data
                                    // collected from the user into a single logical response. each run of the script has its own script.Id so that responses can be
                                    // grouped across runs. this is the difference between scriptId and runId in the following line.
                                    await script.Runner.Probe.StoreDatumAsync(new ScriptDatum(input.CompletionTimestamp.GetValueOrDefault(DateTimeOffset.UtcNow), script.Runner.Script.Id, script.Runner.Name, input.GroupId, input.Id, script.Id, input.Value, script.CurrentDatum?.Id, input.Latitude, input.Longitude, input.LocationUpdateTimestamp, script.RunTime.Value, input.CompletionRecords, input.SubmissionTimestamp.Value), default(CancellationToken));

                                    // once inputs are stored, they should not be stored again, nor should the user be able to modify them if the script is viewed again.
                                    input.NeedsToBeStored = false;
                                    SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(() => input.Enabled = false);
                                }
                            }
                        }
                    }

                    if (script.Valid)
                    {
                        // add completion time and remove all completion times before the participation horizon
                        lock (script.Runner.CompletionTimes)
                        {
                            script.Runner.CompletionTimes.Add(DateTime.Now);
                            script.Runner.CompletionTimes.RemoveAll(completionTime => completionTime < script.Runner.Probe.Protocol.ParticipationHorizon);
                        }
                    }

                    if (!canceled)
                    {
                        SensusServiceHelper.Get().RemoveScript(script);
                    }

                    SensusServiceHelper.Get().Logger.Log("\"" + script.Runner.Name + "\" has finished running.", LoggingLevel.Normal, typeof(Script));
                });
            };

            // don't show list when there are no surveys
            scriptList.BindingContext = SensusServiceHelper.Get().ScriptsToRun;
            scriptList.SetBinding(IsVisibleProperty, new Binding("Count", converter: new ViewVisibleValueConverter(), converterParameter: false));

            // display an informative message when there are no surveys
            Label noSurveysLabel = new Label
            {
                Text              = "You have no pending surveys.",
                TextColor         = Color.Accent,
                FontSize          = 20,
                VerticalOptions   = LayoutOptions.Center,
                HorizontalOptions = LayoutOptions.Center
            };

            noSurveysLabel.BindingContext = SensusServiceHelper.Get().ScriptsToRun;
            noSurveysLabel.SetBinding(IsVisibleProperty, new Binding("Count", converter: new ViewVisibleValueConverter(), converterParameter: true));

            Grid contentGrid = new Grid
            {
                RowDefinitions = { new RowDefinition {
                                       Height = new GridLength(1, GridUnitType.Star)
                                   } },
                ColumnDefinitions = { new ColumnDefinition {
                                          Width = new GridLength(1, GridUnitType.Star)
                                      } },
                VerticalOptions = LayoutOptions.FillAndExpand
            };

            contentGrid.Children.Add(noSurveysLabel, 0, 0);
            contentGrid.Children.Add(scriptList, 0, 0);

            Content = contentGrid;

            System.Timers.Timer filterTimer = new System.Timers.Timer(1000);

            filterTimer.Elapsed += (sender, e) =>
            {
                SensusServiceHelper.Get().RemoveExpiredScripts(true);
            };

            Appearing += (sender, e) =>
            {
                filterTimer.Start();
            };

            Disappearing += (sender, e) =>
            {
                filterTimer.Stop();
            };
        }