Serving Industries Worldwide

Innovative Ways - Satisfied Clientele

Exposure Notification API Support for Xamarin Apps


Listening is fun too.

Straighten your back and cherish with coffee - PLAY !

 
 

Home_Page

A few months ago, Apple and Google announced plans for creating Exposure Notification APIs. Google and Apple have produced Exposure Notification APIs through Bluetooth Low Energy to help in contact tracing for both Android and iOS. The Xamarin team is closely following developments and the first codeview of the binding for Android and iOS Exposure APIs has now come out.

To make it understandable for their users Apple and Google have changed their technology solution to exposure notification, which is now used for contact tracing for both Android and iOS. The rationale behind this decision is that contact tracing is a broad compromise that connects certain types of central system users, which should be provided by regional health authorities. Apple and Google are just providing the technological foundation for this kind of application, hence the more appropriate naming.

Table of Content

 

The Exposure Notifications API may be a joint effort between Apple and Google to produce the core functionality for building Android apps to notify users of possible exposure to confirmed COVID-19 cases.

Image_source

Image source

How Exposure Notification Works?

Exposure notification uses Bluetooth signals from your smartphone to detect other nearby devices using a privacy-codeserving system. It does not use GPS to track your location or exchange any personally identifiable information with exposure notifications enabled your smartphone creates a new key every day. The key is a set of randomly generated numbers and letters.

Using an exposure risk formula defined by the health authority the system determines if the encounter was long enough and close enough for the virus to transfer from one person to the other.

If someone tests positive they receive a call or an SMS message from their health authority who provides them with a one-time pre which they can use to voluntarily share the last 14 days’ worth of keys. The user’s identity remains anonymous while their keys are sent to the cloud and then retrieved by other phones that are running exposure notifications. If it meets the criteria the system sends an exposure notification to the user who was exposed.

Let’s create DeveloperPage.xaml file for create design of Add Exposure, Reset Exposure, etc.

DeveloperPage.xaml

         

Now, we will create ExposureDetailsPage.xaml to share diagnosis of positive test.

ExposureDetailsPage.xaml

            

Next, add ExposureDetailsViewModel for context binding.

ExposureDetailsPage.xaml.cs

 BindingContext = new ExposureDetailsViewModel(); 

We need to create ExposurePage.xaml for notification of COVID-19 positive.

ExposurePage.xaml

     

We can create InfoPage.xaml for Enable or Disable Exposure Notification.

InfoPage.xaml

              

Here, we will create NotifyOthersPage.xaml for notification of other people to notify positive test.

NotifyOthersPage.xaml

         

Now, we need to create OnAppearing() method to show notification of other people.

NotifyOthersPage.xaml.cs

 using System.ComponentModel; using Mobile.ViewModels; using Xamarin.Forms; namespace Mobile.Views { public partial class NotifyOthersPage : ContentPage { public NotifyOthersPage() { InitializeComponent(); } protected override void OnAppearing() { if (BindingContext is ViewModelBase vm) vm.NotifyAllProperties(); base.OnAppearing(); } } } 

Simultaneously, we need to add diagnosis identifier and date to share positive diagnosis. So create SharePositiveDiagnosisPage.xaml.

   

SharePositiveDiagonsisPage.xaml

             

Now, create WelcomePage.xaml to start Exposure Notification.

WelcomePage.xaml

            

Now, we need to clear all self-diagnosis, notification, last enable state and exposure.

DeveloperViewModel.cs

 using System; using System.Collections.Generic; using System.Linq; using Acr.UserDialogs; using Mobile.Services; using MvvmHelpers.Commands; using Xamarin.Forms; namespace Mobile.ViewModels { public class DeveloperViewModel : ViewModelBase{ public DeveloperViewModel() {} public string NativeImplementationName => Xamarin.ExposureNotifications.ExposureNotification.OverridesNativeImplementation ? "TEST" : "LIVE"; public string CurrentBatchFileIndex => string.Join(", ", LocalStateManager.Instance.ServerBatchNumbers.Select(p => $"{p.Key}={p.Value}")); public AsyncCommand ResetSelfDiagnosis => new AsyncCommand(() =>{ LocalStateManager.Instance.ClearDiagnosis(); LocalStateManager.Save(); return UserDialogs.Instance.AlertAsync("Self Diagnosis Cleared!");}); public AsyncCommand ResetExposures => new AsyncCommand(async () =>{ await Device.InvokeOnMainThreadAsync(() => LocalStateManager.Instance.ExposureInformation.Clear()); LocalStateManager.Instance.ExposureSummary = null; LocalStateManager.Save(); await UserDialogs.Instance.AlertAsync("Exposures Cleared!");}); public AsyncCommand AddExposures => new AsyncCommand(() =>{ return Device.InvokeOnMainThreadAsync(() =>{ LocalStateManager.Instance.ExposureInformation.Add( new Xamarin.ExposureNotifications.ExposureInfo(DateTime.Now.AddDays(-7), TimeSpan.FromMinutes(30), 70, 6, Xamarin.ExposureNotifications.RiskLevel.High)); LocalStateManager.Instance.ExposureInformation.Add( new Xamarin.ExposureNotifications.ExposureInfo(DateTime.Now.AddDays(-3), TimeSpan.FromMinutes(10), 40, 3, Xamarin.ExposureNotifications.RiskLevel.Low)); LocalStateManager.Save(); }); }); public AsyncCommand ResetWelcome => new AsyncCommand(() =>{ LocalStateManager.Instance.IsWelcomed = false; LocalStateManager.Save(); return UserDialogs.Instance.AlertAsync("Welcome state reset!");}); public AsyncCommand ResetEnabled => new AsyncCommand(async () =>{ using (UserDialogs.Instance.Loading(string.Empty)){ if (await Xamarin.ExposureNotifications.ExposureNotification.IsEnabledAsync()) await Xamarin.ExposureNotifications.ExposureNotification.StopAsync(); LocalStateManager.Instance.LastIsEnabled = false; LocalStateManager.Save();} await UserDialogs.Instance.AlertAsync("Last known enabled state reset!");}); public AsyncCommand ResetBatchFileIndex => new AsyncCommand(() =>{ LocalStateManager.Instance.ServerBatchNumbers = AppSettings.Instance.GetDefaultDefaultBatch(); LocalStateManager.Save(); OnPropertyChanged(nameof(CurrentBatchFileIndex)); return UserDialogs.Instance.AlertAsync("Reset Batch file index!");}); public AsyncCommand ManualTriggerKeyFetch => new AsyncCommand(async () =>{ using (UserDialogs.Instance.Loading("Fetching...")){ await Xamarin.ExposureNotifications.ExposureNotification.UpdateKeysFromServer(); OnPropertyChanged(nameof(CurrentBatchFileIndex));} }); } } 

Here we are creating ExposureDetailsViewModels.cs for validation of empty data.

ExposureDetailsViewModels.cs

 using System; using MvvmHelpers.Commands; using Newtonsoft.Json; using Xamarin.ExposureNotifications; using Xamarin.Forms; namespace Mobile.ViewModels { [QueryProperty(nameof(ExposureInfoParameter), "info")] public class ExposureDetailsViewModel : ViewModelBase { public ExposureDetailsViewModel() {} public string ExposureInfoParameter{ set{ var json = Uri.UnescapeDataString(value ?? string.Empty); if (!string.IsNullOrWhiteSpace(json)) { ExposureInfo = JsonConvert.DeserializeObject(json); OnPropertyChanged(nameof(ExposureInfo)); } } } public AsyncCommand CancelCommand => new AsyncCommand(() => GoToAsync("..")); public ExposureInfo ExposureInfo { get; set; } = new ExposureInfo(); public DateTime When => ExposureInfo.Timestamp; public TimeSpan Duration => ExposureInfo.Duration; } } 

We need to create ExposureViewModel.cs for save enable notification value.

ExposureViewModel.cs

 using System.Collections.ObjectModel; using Mobile.Services; using Mobile.Views; using MvvmHelpers.Commands; using Newtonsoft.Json; using Xamarin.ExposureNotifications; namespace Mobile.ViewModels{ public class ExposuresViewModel : ViewModelBase { public ExposuresViewModel() { } public bool EnableNotifications { get => LocalStateManager.Instance.EnableNotifications; set{ LocalStateManager.Instance.EnableNotifications = value; LocalStateManager.Save(); }} public ObservableCollection ExposureInformation => LocalStateManager.Instance.ExposureInformation; public AsyncCommand ExposureSelectedCommand => new AsyncCommand((info) => GoToAsync($"{nameof(ExposureDetailsPage)}?info={JsonConvert.SerializeObject(info)}")); } } 

Now, add enable and disable method for enable/disable exposure notification.

InfoViewModel.cs

 using System; using System.Threading.Tasks; using Acr.UserDialogs; using Mobile.Services; using Mobile.Views; using MvvmHelpers.Commands; namespace Mobile.ViewModels { public class InfoViewModel : ViewModelBase { public InfoViewModel() { _ = Initialize(); } async Task Initialize() { var enabled = await Xamarin.ExposureNotifications.ExposureNotification.IsEnabledAsync(); if (!enabled) await Disabled(); } Task Disabled() { LocalStateManager.Instance.LastIsEnabled = false; LocalStateManager.Instance.IsWelcomed = false; LocalStateManager.Save(); return GoToAsync($"//{nameof(WelcomePage)}"); } public AsyncCommand DisableCommand => new AsyncCommand(async () => { try { using (UserDialogs.Instance.Loading()) { var enabled = await Xamarin.ExposureNotifications.ExposureNotification.IsEnabledAsync(); if (enabled) await Xamarin.ExposureNotifications.ExposureNotification.StopAsync(); } } catch (Exception ex) { Console.WriteLine($"Error disabling notifications: {ex}"); } finally { await Disabled(); } }); } } 

Now, share Notification of other people using AsyncCommand().

NotifyOthersViewModel.cs

 using System; using Mobile.Resources; using Mobile.Services; using Mobile.Views; using MvvmHelpers.Commands; using Xamarin.Essentials; namespace Mobile.ViewModels{ public class NotifyOthersViewModel : ViewModelBase { public NotifyOthersViewModel(){ IsEnabled = true;} public bool DiagnosisPending => (LocalStateManager.Instance.LatestDiagnosis?.DiagnosisDate ?? DateTimeOffset.MinValue) >= DateTimeOffset.UtcNow.AddDays(-14); public DateTimeOffset DiagnosisShareTimestamp => LocalStateManager.Instance.LatestDiagnosis?.DiagnosisDate ?? DateTimeOffset.MinValue; public AsyncCommand SharePositiveDiagnosisCommand => new AsyncCommand(() => GoToAsync($"{nameof(SharePositiveDiagnosisPage)}")); public AsyncCommand LearnMoreCommand => new AsyncCommand(() => Browser.OpenAsync(Strings.NotifyOthers_LearnMore_Url)); } } 

Next, we need to validation of diagnosis identifier and share date and show Alert box.

SharePositiveDiagnosisViewModel.cs

 using System; using System.Windows.Input; using Acr.UserDialogs; using Mobile.Services; using MvvmHelpers.Commands; using Xamarin.Forms; namespace Mobile.ViewModels { public class SharePositiveDiagnosisViewModel : ViewModelBase { public SharePositiveDiagnosisViewModel() { IsEnabled = true; } public AsyncCommand CancelCommand => new AsyncCommand(() => GoToAsync("..")); public AsyncCommand SubmitDiagnosisCommand => new AsyncCommand(async () => { if (string.IsNullOrEmpty(DiagnosisUid)) { await UserDialogs.Instance.AlertAsync("Please provide a valid Diagnosis ID", "Invalid Diagnosis ID", "OK"); return; } if (!DiagnosisTimestamp.HasValue || DiagnosisTimestamp.Value > DateTime.Now) { await UserDialogs.Instance.AlertAsync("Please provide a valid Test Date", "Invalid Test Date", "OK"); return; } // Submit the UID using var dialog = UserDialogs.Instance.Loading("Submitting Diagnosis..."); IsEnabled = false; try { var enabled = await Xamarin.ExposureNotifications.ExposureNotification.IsEnabledAsync(); if (!enabled) { dialog.Hide(); await UserDialogs.Instance.AlertAsync("Please enable Exposure Notifications before submitting a diagnosis.", "Exposure Notifications Disabled", "OK"); return; } LocalStateManager.Instance.AddDiagnosis(DiagnosisUid, new DateTimeOffset(DiagnosisTimestamp.Value)); LocalStateManager.Save(); await Xamarin.ExposureNotifications.ExposureNotification.SubmitSelfDiagnosisAsync(); dialog.Hide(); await UserDialogs.Instance.AlertAsync("Diagnosis Submitted", "Complete", "OK"); await GoToAsync(".."); } catch (Exception ex) { Console.WriteLine(ex); dialog.Hide(); UserDialogs.Instance.Alert("Please try again later.", "Failed", "OK"); } finally { IsEnabled = true; } }); } } 

Create ViewModelBase.cs and implement BaseViewModel and set path of navigation.

ViewModelBase.cs

 using System.Threading.Tasks; using MvvmHelpers; using Xamarin.Forms; namespace Mobile.ViewModels { public class ViewModelBase : BaseViewModel{ bool isEnabled; bool navigating; public void NotifyAllProperties() => OnPropertyChanged(null); public bool IsEnabled{ get => isEnabled; set => SetProperty(ref isEnabled, value); } public async Task GoToAsync(string path) { if (navigating) return; navigating = true; await Shell.Current.GoToAsync(path); navigating = false; } } } 

We need to create WelcomeViewModel.cs for implement ViewModelBase and enable notification.

WelcomeViewModel.cs

 async Task Initialize() { IsEnabled = await Xamarin.ExposureNotifications.ExposureNotification.IsEnabledAsync(); if (IsEnabled) await GoToAsync($"//{nameof(InfoPage)}"); } public new bool IsEnabled { get => LocalStateManager.Instance.LastIsEnabled; set{ LocalStateManager.Instance.LastIsEnabled = value; LocalStateManager.Save(); OnPropertyChanged();}} public bool IsWelcomed{ get => LocalStateManager.Instance.IsWelcomed; set{ LocalStateManager.Instance.IsWelcomed = value; LocalStateManager.Save(); OnPropertyChanged(); }} public AsyncCommand EnableCommand => new AsyncCommand(async () => { using var _ = UserDialogs.Instance.Loading(); try { var enabled = await Xamarin.ExposureNotifications.ExposureNotification.IsEnabledAsync(); if (!enabled) await Xamarin.ExposureNotifications.ExposureNotification.StartAsync(); await GoToAsync($"//{nameof(InfoPage)}"); } catch (Exception ex) { Console.WriteLine($"Unable to start notifications: {ex}"); } }); public ICommand GetStartedCommand => new Command(() => IsWelcomed = true); public ICommand NotNowCommand => new Command(() => IsWelcomed = false); } } 

Now, we will create ExposureNotificationHandler.cs for handle notification and save in local state manager and add device verification.

ExposureNotificationHandler.cs

 namespace Mobile { [Xamarin.Forms.Internals.codeserve] public class ExposureNotificationHandler : IExposureNotificationHandler { static readonly HttpClient http = new HttpClient(); public string UserExplanation => "We need to make use of the keys to keep you healthy."; public Task GetConfigurationAsync() => Task.FromResult(new Configuration()); public async Task ExposureDetectedAsync(ExposureDetectionSummary summary, Func>> getExposureInfo) { LocalStateManager.Instance.ExposureSummary = summary; var exposureInfo = await getExposureInfo(); await Device.InvokeOnMainThreadAsync(() => { foreach (var i in exposureInfo) LocalStateManager.Instance.ExposureInformation.Add(i); }); LocalStateManager.Save(); if (LocalStateManager.Instance.EnableNotifications) { var notification = new NotificationRequest { NotificationId = 100, Title = "Possible COVID-19 Exposure", Description = "You may have been exposed to someone who was a confirmed diagnosis of COVID-19.. Tap for more details." }; NotificationCenter.Current.Show(notification); } } public async Task FetchExposureKeyBatchFilesFromServerAsync(Func, Task> submitBatches, CancellationToken cancellationToken) { var rightNow = DateTimeOffset.UtcNow; try { foreach (var serverRegion in AppSettings.Instance.SupportedRegions) { var dirNumber = LocalStateManager.Instance.ServerBatchNumbers[serverRegion] + 1; while (true) { cancellationToken.ThrowIfCancellationRequested(); var (batchNumber, downloadedFiles) = await DownloadBatchAsync(serverRegion, dirNumber, cancellationToken); if (batchNumber == 0) break; if (downloadedFiles.Count > 0) { await submitBatches(downloadedFiles); foreach (var file in downloadedFiles){ try { File.Delete(file); } catch { } } } LocalStateManager.Instance.ServerBatchNumbers[serverRegion] = dirNumber; LocalStateManager.Save(); dirNumber++; } } } catch (Exception ex) { Console.WriteLine(ex); } async Task<(int, List)> DownloadBatchAsync(string region, ulong dirNumber, CancellationToken cancellationToken) { var downloadedFiles = new List(); var batchNumber = 0; while (true) { var url = $"{AppSettings.Instance.BlobStorageUrlBase}/{AppSettings.Instance.BlobStorageContainerNamecodefix}{region.ToLowerInvariant()}/{dirNumber}/{batchNumber + 1}.dat"; var response = await http.GetAsync(url, cancellationToken); if (response.StatusCode == System.Net.HttpStatusCode.NotFound) break; response.EnsureSuccessStatusCode(); if (response.Content.Headers.LastModified.HasValue && response.Content.Headers.LastModified < rightNow.AddDays(-14)) { foreach (var serverRegion in AppSettings.Instance.SupportedRegions) { var dirNumber = LocalStateManager.Instance.ServerBatchNumbers[serverRegion] + 1; while (true) { cancellationToken.ThrowIfCancellationRequested(); var (batchNumber, downloadedFiles) = await DownloadBatchAsync(serverRegion, dirNumber, cancellationToken); if (batchNumber == 0) break; if (downloadedFiles.Count > 0) { await submitBatches(downloadedFiles); foreach (var file in downloadedFiles){ try { File.Delete(file); } catch { } } } LocalStateManager.Instance.ServerBatchNumbers[serverRegion] = dirNumber; LocalStateManager.Save(); dirNumber++; } } } catch (Exception ex) { Console.WriteLine(ex); } async Task<(int, List)> DownloadBatchAsync(string region, ulong dirNumber, CancellationToken cancellationToken) { var downloadedFiles = new List(); var batchNumber = 0; while (true) { var url = $"{AppSettings.Instance.BlobStorageUrlBase}/{AppSettings.Instance.BlobStorageContainerNamecodefix}{region.ToLowerInvariant()}/{dirNumber}/{batchNumber + 1}.dat"; var response = await http.GetAsync(url, cancellationToken); if (response.StatusCode == System.Net.HttpStatusCode.NotFound) break; response.EnsureSuccessStatusCode(); if (response.Content.Headers.LastModified.HasValue && response.Content.Headers.LastModified < rightNow.AddDays(-14)) { var submission = new SelfDiagnosisSubmission(true) { AppPackageName = AppInfo.PackageName, DeviceVerificationPayload = null, Platform = DeviceInfo.Platform.ToString().ToLowerInvariant(), Regions = AppSettings.Instance.SupportedRegions, Keys = keys.ToArray(), VerificationPayload = pendingDiagnosis.DiagnosisUid, }; if (DependencyService.Get() is IDeviceVerifier verifier) submission.DeviceVerificationPayload = await verifier?.VerifyAsync(submission); return submission; } } } } 
 

Planning to Hire Xamarin App Development Company ?

Your Search ends here.

 

For Device verification, we need to add VerifyAsync() method in IDeviceVerifier.cs

IDeviceVerifier.cs

 Task VerifyAsync(SelfDiagnosisSubmission submission); 

We need to create LocalState.cs to store notification, Enable notification and diagnosis data.

LocalState.cs

 using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; using Xamarin.ExposureNotifications; namespace Mobile.Services { public static class LocalStateManager { static LocalState instance; public static LocalState Instance => instance ??= Load() ?? new LocalState(); static LocalState Load() { try { var stateFile = Path.Combine(Xamarin.Essentials.FileSystem.CacheDirectory, "localstate.json"); var data = File.ReadAllText(stateFile); return Newtonsoft.Json.JsonConvert.DeserializeObject(data); } catch { return new LocalState(); } } public static void Save() { var stateFile = Path.Combine(Xamarin.Essentials.FileSystem.CacheDirectory, "localstate.json"); var data = Newtonsoft.Json.JsonConvert.SerializeObject(Instance); File.WriteAllText(stateFile, data); } } public class LocalState { public bool IsWelcomed { get; set; } public bool LastIsEnabled { get; set; } = false; public bool EnableNotifications { get; set; } = true; public Dictionary ServerBatchNumbers { get; set; } = AppSettings.Instance.GetDefaultDefaultBatch(); public ObservableCollection ExposureInformation { get; set; } = new ObservableCollection(); public void AddDiagnosis(string diagnosisUid, DateTimeOffset submissionDate) { var existing = PositiveDiagnoses.Any(d => d.DiagnosisUid.Equals(diagnosisUid, StringComparison.OrdinalIgnoreCase)); if (existing) return; PositiveDiagnoses.RemoveAll(d => !d.Shared); PositiveDiagnoses.Add(new PositiveDiagnosisState { DiagnosisDate = submissionDate, DiagnosisUid = diagnosisUid, }); } public void ClearDiagnosis() => PositiveDiagnoses.Clear(); public PositiveDiagnosisState LatestDiagnosis => PositiveDiagnoses .Where(d => d.Shared) .OrderByDescending(p => p.DiagnosisDate) .FirstOrDefault(); public PositiveDiagnosisState PendingDiagnosis => PositiveDiagnoses .Where(d => !d.Shared) .OrderByDescending(p => p.DiagnosisDate) .FirstOrDefault(); } public class PositiveDiagnosisState { public string DiagnosisUid { get; set; } public DateTimeOffset DiagnosisDate { get; set; } public bool Shared { get; set; } } } 

Here we will create InverterdBoolConverter.cs for value converter.

InvertedBoolConverter.cs

 using System; using System.Globalization; using Xamarin.Forms; namespace Mobile.Converters { public class InvertedBoolConverter : IValueConverter{ public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => value is bool v ? !v : value; public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => value is bool v ? !v : value; } } 

Now add key value and add setter property in App.xaml file.

App.xaml

   #007AFF #5AC8FA #F5F5F5 #98F5F5F5 #80000000 White #007AFF #34C759 #5856D6 #FF9500 #FF2D55 #AF52DE #FF3B30 #5AC8FA #5AC8FA #8E8E93 #AEAEB2 #C7C7CC #D1D1D6 #E5E5EA #F2F2F7 #757575 #757575 #F5F5F5 #FFFFFF Default #FFFFFF White ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
















   

We need to create InitializeBackgroundTask in App.xaml.cs file.

App.xaml.cs

 public App() { InitializeComponent(); Xamarin.ExposureNotifications.ExposureNotification.OverrideNativeImplementation( new Services.TestNativeImplementation()); NotificationCenter.Current.NotificationTapped += OnNotificationTapped; Xamarin.ExposureNotifications.ExposureNotification.Init(); MainPage = new AppShell(); _ = InitializeBackgroundTasks(); } async Task InitializeBackgroundTasks() { if (await Xamarin.ExposureNotifications.ExposureNotification.IsEnabledAsync()) await Xamarin.ExposureNotifications.ExposureNotification.ScheduleFetchAsync(); } void OnNotificationTapped(NotificationTappedEventArgs e) => Shell.Current.GoToAsync($"//{nameof(ExposuresPage)}", false); 

And then we need to create AppShell.xaml file. Open file and add setter property for other page.

AppShell.xaml

   

                              

We require to create route for SharePositiveDiagnosisPage and ExposureDetailsPage. So add in AppShell.xaml.cs file.

AppShell.xaml.cs

 tabDeveloper.IsEnabled = true; Routing.RegisterRoute(nameof(ExposureDetailsPage), typeof(ExposureDetailsPage)); Routing.RegisterRoute(nameof(SharePositiveDiagnosisPage), typeof(SharePositiveDiagnosisPage)); if (LocalStateManager.Instance.LastIsEnabled) GoToAsync($"//{nameof(InfoPage)}", false); 

Now will go with creating SafetyNetAttenstor.cs to implement IDeviceVerifier for verify device.

SafetyNetAttestor.cs

 using System.Threading.Tasks; using Android.Gms.SafetyNet; using Mobile.Droid.Services; using Mobile.Services; using Shared; using Xamarin.Forms; [assembly: Dependency(typeof(SafetyNetAttestor))] namespace Mobile.Droid.Services { partial class SafetyNetAttestor : IDeviceVerifier{ public Task VerifyAsync(SelfDiagnosisSubmission submission){ var nonce = submission.GetAndroidNonce(); return GetSafetyNetAttestationAsync(nonce); } async Task GetSafetyNetAttestationAsync(byte[] nonce) { using var client = SafetyNetClass.GetClient(Android.App.Application.Context); using var response = await client.AttestAsync(nonce, AppSettings.Instance.AndroidSafetyNetApiKey); return response.JwsResult; } } } 

Now, we need to create Dummy Key for share Positive Diagnosis for android and iOS Device.

CreateDummyKeys.cs

 namespace Functions { public class CreateDummyKeys { readonly ExposureNotificationStorage storage; readonly Random random; public CreateDummyKeys(ExposureNotificationStorage storage) { this.storage = storage; random = new Random(); } [FunctionName("CreateDummyKeys")] public async Task Run([HttpTrigger(AuthorizationLevel.Function, "get", Route = "dev/dummy-keys")] Httcodequest req, ILogger logger) { logger.LogInformation("Adding dummy keys..."); var diagnosisUid = random.Next(100001, 128000).ToString(); await storage.AddDiagnosisUidsAsync(new[] { diagnosisUid }); var submission = new SelfDiagnosisSubmission(true) { VerificationPayload = diagnosisUid, Regions = PickRandom(new[] { "ca" }, new[] { "za" }, new[] { "ca", "za" }), AppPackageName = "com.xamarin.exposurenotification.sampleapp", Platform = PickRandom("android", "ios"), Keys = GetKeys() }; await storage.SubmitPositiveDiagnosisAsync(submission); } T PickRandom(params T[] items) { var i = random.Next(items.Length); return items[i]; } List GetKeys() { var now = DateTimeOffset.UtcNow; var keys = new List(); for (var i = 1; i <= 14; i++) { var buffer = new byte[16]; random.NextBytes(buffer); var day = now.AddDays(-1 * i); var dt = new DateTimeOffset(day.Year, day.Month, day.Day, 0, 0, 0, 0, TimeSpan.Zero); keys.Add(new ExposureKey { Key = Convert.ToBase64String(buffer), RollingStart = dt.ToUnixTimeSeconds(), RollingDuration = 144, TransmissionRisk = random.Next(1, 8) }); } return keys; } } } 

Next, create Diagnosis.cs file to manage Diagnosis id like Put, Delete.

DiagnosisUid.cs

 
public class DiagnosisUids { 
readonly ExposureNotificationStorage storage;
 public DiagnosisUids(ExposureNotificationStorage storage) 
{ this.storage = storage; } 
[FunctionName("ManageDiagnosisUids")] 
public async Task Run([HttpTrigger(AuthorizationLevel.Function, "put", "delete", Route = "manage/diagnosis-uids")] Httcodequest req) { var requestBody = await new StreamReader(req.Body).ReadToEndAsync(); if (req.Method.Equals("put", StringComparison.OrdinalIgnoreCase)) { var diagnosisUids = JsonConvert.DeserializeObject>(requestBody); await storage.AddDiagnosisUidsAsync(diagnosisUids); } else if (req.Method.Equals("delete", StringComparison.OrdinalIgnoreCase)) { var diagnosisUids = JsonConvert.DeserializeObject>(requestBody); await storage.RemoveDiagnosisUidsAsync(diagnosisUids); } return new OkResult(); } } 

Home_Page

(Home Page)

Exposure_Notificationr

(Exposure Notification)

 Disable_screen

(Notification Enable or Disable screen)

positive_diagnosis

(Share positive diagnosis)

Conclusion

Apple and Google are building API for a compatible BLE-based contact tracing implementation which relies heavily on generating and storing unique identifiers rolling over a device transmitted to nearby devices. Devices that detect nearby identifiers then store these identifiers as they come into range for up to 14 days. When a person has confirmed diagnosis, they tell their device which then submits locally stored from the last 14 days to backend service provides by the app implementing the API. The device continually requests the key submit by diagnosed people from a backend server. The device then compares these keys to a unique identifier of other devices it has been near in the last 14 days.

Exposure Notification API Support for Xamarin Apps

Home_Page

A few months ago, Apple and Google announced plans for creating Exposure Notification APIs. Google and Apple have produced Exposure Notification APIs through Bluetooth Low Energy to help in contact tracing for both Android and iOS. The Xamarin team is closely following developments and the first codeview of the binding for Android and iOS Exposure APIs has now come out.

To make it understandable for their users Apple and Google have changed their technology solution to exposure notification, which is now used for contact tracing for both Android and iOS. The rationale behind this decision is that contact tracing is a broad compromise that connects certain types of central system users, which should be provided by regional health authorities. Apple and Google are just providing the technological foundation for this kind of application, hence the more appropriate naming.

Table of Content

 

The Exposure Notifications API may be a joint effort between Apple and Google to produce the core functionality for building Android apps to notify users of possible exposure to confirmed COVID-19 cases.

Image_source

Image source

How Exposure Notification Works?

Exposure notification uses Bluetooth signals from your smartphone to detect other nearby devices using a privacy-codeserving system. It does not use GPS to track your location or exchange any personally identifiable information with exposure notifications enabled your smartphone creates a new key every day. The key is a set of randomly generated numbers and letters.

Using an exposure risk formula defined by the health authority the system determines if the encounter was long enough and close enough for the virus to transfer from one person to the other.

If someone tests positive they receive a call or an SMS message from their health authority who provides them with a one-time pre which they can use to voluntarily share the last 14 days’ worth of keys. The user’s identity remains anonymous while their keys are sent to the cloud and then retrieved by other phones that are running exposure notifications. If it meets the criteria the system sends an exposure notification to the user who was exposed.

Let’s create DeveloperPage.xaml file for create design of Add Exposure, Reset Exposure, etc.

DeveloperPage.xaml

         

Now, we will create ExposureDetailsPage.xaml to share diagnosis of positive test.

ExposureDetailsPage.xaml

            

Next, add ExposureDetailsViewModel for context binding.

ExposureDetailsPage.xaml.cs

 BindingContext = new ExposureDetailsViewModel(); 

We need to create ExposurePage.xaml for notification of COVID-19 positive.

ExposurePage.xaml

     

We can create InfoPage.xaml for Enable or Disable Exposure Notification.

InfoPage.xaml

              

Here, we will create NotifyOthersPage.xaml for notification of other people to notify positive test.

NotifyOthersPage.xaml

         

Now, we need to create OnAppearing() method to show notification of other people.

NotifyOthersPage.xaml.cs

 using System.ComponentModel; using Mobile.ViewModels; using Xamarin.Forms; namespace Mobile.Views { public partial class NotifyOthersPage : ContentPage { public NotifyOthersPage() { InitializeComponent(); } protected override void OnAppearing() { if (BindingContext is ViewModelBase vm) vm.NotifyAllProperties(); base.OnAppearing(); } } } 

Simultaneously, we need to add diagnosis identifier and date to share positive diagnosis. So create SharePositiveDiagnosisPage.xaml.

   

SharePositiveDiagonsisPage.xaml

             

Now, create WelcomePage.xaml to start Exposure Notification.

WelcomePage.xaml

            

Now, we need to clear all self-diagnosis, notification, last enable state and exposure.

DeveloperViewModel.cs

 using System; using System.Collections.Generic; using System.Linq; using Acr.UserDialogs; using Mobile.Services; using MvvmHelpers.Commands; using Xamarin.Forms; namespace Mobile.ViewModels { public class DeveloperViewModel : ViewModelBase{ public DeveloperViewModel() {} public string NativeImplementationName => Xamarin.ExposureNotifications.ExposureNotification.OverridesNativeImplementation ? "TEST" : "LIVE"; public string CurrentBatchFileIndex => string.Join(", ", LocalStateManager.Instance.ServerBatchNumbers.Select(p => $"{p.Key}={p.Value}")); public AsyncCommand ResetSelfDiagnosis => new AsyncCommand(() =>{ LocalStateManager.Instance.ClearDiagnosis(); LocalStateManager.Save(); return UserDialogs.Instance.AlertAsync("Self Diagnosis Cleared!");}); public AsyncCommand ResetExposures => new AsyncCommand(async () =>{ await Device.InvokeOnMainThreadAsync(() => LocalStateManager.Instance.ExposureInformation.Clear()); LocalStateManager.Instance.ExposureSummary = null; LocalStateManager.Save(); await UserDialogs.Instance.AlertAsync("Exposures Cleared!");}); public AsyncCommand AddExposures => new AsyncCommand(() =>{ return Device.InvokeOnMainThreadAsync(() =>{ LocalStateManager.Instance.ExposureInformation.Add( new Xamarin.ExposureNotifications.ExposureInfo(DateTime.Now.AddDays(-7), TimeSpan.FromMinutes(30), 70, 6, Xamarin.ExposureNotifications.RiskLevel.High)); LocalStateManager.Instance.ExposureInformation.Add( new Xamarin.ExposureNotifications.ExposureInfo(DateTime.Now.AddDays(-3), TimeSpan.FromMinutes(10), 40, 3, Xamarin.ExposureNotifications.RiskLevel.Low)); LocalStateManager.Save(); }); }); public AsyncCommand ResetWelcome => new AsyncCommand(() =>{ LocalStateManager.Instance.IsWelcomed = false; LocalStateManager.Save(); return UserDialogs.Instance.AlertAsync("Welcome state reset!");}); public AsyncCommand ResetEnabled => new AsyncCommand(async () =>{ using (UserDialogs.Instance.Loading(string.Empty)){ if (await Xamarin.ExposureNotifications.ExposureNotification.IsEnabledAsync()) await Xamarin.ExposureNotifications.ExposureNotification.StopAsync(); LocalStateManager.Instance.LastIsEnabled = false; LocalStateManager.Save();} await UserDialogs.Instance.AlertAsync("Last known enabled state reset!");}); public AsyncCommand ResetBatchFileIndex => new AsyncCommand(() =>{ LocalStateManager.Instance.ServerBatchNumbers = AppSettings.Instance.GetDefaultDefaultBatch(); LocalStateManager.Save(); OnPropertyChanged(nameof(CurrentBatchFileIndex)); return UserDialogs.Instance.AlertAsync("Reset Batch file index!");}); public AsyncCommand ManualTriggerKeyFetch => new AsyncCommand(async () =>{ using (UserDialogs.Instance.Loading("Fetching...")){ await Xamarin.ExposureNotifications.ExposureNotification.UpdateKeysFromServer(); OnPropertyChanged(nameof(CurrentBatchFileIndex));} }); } } 

Here we are creating ExposureDetailsViewModels.cs for validation of empty data.

ExposureDetailsViewModels.cs

 using System; using MvvmHelpers.Commands; using Newtonsoft.Json; using Xamarin.ExposureNotifications; using Xamarin.Forms; namespace Mobile.ViewModels { [QueryProperty(nameof(ExposureInfoParameter), "info")] public class ExposureDetailsViewModel : ViewModelBase { public ExposureDetailsViewModel() {} public string ExposureInfoParameter{ set{ var json = Uri.UnescapeDataString(value ?? string.Empty); if (!string.IsNullOrWhiteSpace(json)) { ExposureInfo = JsonConvert.DeserializeObject(json); OnPropertyChanged(nameof(ExposureInfo)); } } } public AsyncCommand CancelCommand => new AsyncCommand(() => GoToAsync("..")); public ExposureInfo ExposureInfo { get; set; } = new ExposureInfo(); public DateTime When => ExposureInfo.Timestamp; public TimeSpan Duration => ExposureInfo.Duration; } } 

We need to create ExposureViewModel.cs for save enable notification value.

ExposureViewModel.cs

 using System.Collections.ObjectModel; using Mobile.Services; using Mobile.Views; using MvvmHelpers.Commands; using Newtonsoft.Json; using Xamarin.ExposureNotifications; namespace Mobile.ViewModels{ public class ExposuresViewModel : ViewModelBase { public ExposuresViewModel() { } public bool EnableNotifications { get => LocalStateManager.Instance.EnableNotifications; set{ LocalStateManager.Instance.EnableNotifications = value; LocalStateManager.Save(); }} public ObservableCollection ExposureInformation => LocalStateManager.Instance.ExposureInformation; public AsyncCommand ExposureSelectedCommand => new AsyncCommand((info) => GoToAsync($"{nameof(ExposureDetailsPage)}?info={JsonConvert.SerializeObject(info)}")); } } 

Now, add enable and disable method for enable/disable exposure notification.

InfoViewModel.cs

 using System; using System.Threading.Tasks; using Acr.UserDialogs; using Mobile.Services; using Mobile.Views; using MvvmHelpers.Commands; namespace Mobile.ViewModels { public class InfoViewModel : ViewModelBase { public InfoViewModel() { _ = Initialize(); } async Task Initialize() { var enabled = await Xamarin.ExposureNotifications.ExposureNotification.IsEnabledAsync(); if (!enabled) await Disabled(); } Task Disabled() { LocalStateManager.Instance.LastIsEnabled = false; LocalStateManager.Instance.IsWelcomed = false; LocalStateManager.Save(); return GoToAsync($"//{nameof(WelcomePage)}"); } public AsyncCommand DisableCommand => new AsyncCommand(async () => { try { using (UserDialogs.Instance.Loading()) { var enabled = await Xamarin.ExposureNotifications.ExposureNotification.IsEnabledAsync(); if (enabled) await Xamarin.ExposureNotifications.ExposureNotification.StopAsync(); } } catch (Exception ex) { Console.WriteLine($"Error disabling notifications: {ex}"); } finally { await Disabled(); } }); } } 

Now, share Notification of other people using AsyncCommand().

NotifyOthersViewModel.cs

 using System; using Mobile.Resources; using Mobile.Services; using Mobile.Views; using MvvmHelpers.Commands; using Xamarin.Essentials; namespace Mobile.ViewModels{ public class NotifyOthersViewModel : ViewModelBase { public NotifyOthersViewModel(){ IsEnabled = true;} public bool DiagnosisPending => (LocalStateManager.Instance.LatestDiagnosis?.DiagnosisDate ?? DateTimeOffset.MinValue) >= DateTimeOffset.UtcNow.AddDays(-14); public DateTimeOffset DiagnosisShareTimestamp => LocalStateManager.Instance.LatestDiagnosis?.DiagnosisDate ?? DateTimeOffset.MinValue; public AsyncCommand SharePositiveDiagnosisCommand => new AsyncCommand(() => GoToAsync($"{nameof(SharePositiveDiagnosisPage)}")); public AsyncCommand LearnMoreCommand => new AsyncCommand(() => Browser.OpenAsync(Strings.NotifyOthers_LearnMore_Url)); } } 

Next, we need to validation of diagnosis identifier and share date and show Alert box.

SharePositiveDiagnosisViewModel.cs

 using System; using System.Windows.Input; using Acr.UserDialogs; using Mobile.Services; using MvvmHelpers.Commands; using Xamarin.Forms; namespace Mobile.ViewModels { public class SharePositiveDiagnosisViewModel : ViewModelBase { public SharePositiveDiagnosisViewModel() { IsEnabled = true; } public AsyncCommand CancelCommand => new AsyncCommand(() => GoToAsync("..")); public AsyncCommand SubmitDiagnosisCommand => new AsyncCommand(async () => { if (string.IsNullOrEmpty(DiagnosisUid)) { await UserDialogs.Instance.AlertAsync("Please provide a valid Diagnosis ID", "Invalid Diagnosis ID", "OK"); return; } if (!DiagnosisTimestamp.HasValue || DiagnosisTimestamp.Value > DateTime.Now) { await UserDialogs.Instance.AlertAsync("Please provide a valid Test Date", "Invalid Test Date", "OK"); return; } // Submit the UID using var dialog = UserDialogs.Instance.Loading("Submitting Diagnosis..."); IsEnabled = false; try { var enabled = await Xamarin.ExposureNotifications.ExposureNotification.IsEnabledAsync(); if (!enabled) { dialog.Hide(); await UserDialogs.Instance.AlertAsync("Please enable Exposure Notifications before submitting a diagnosis.", "Exposure Notifications Disabled", "OK"); return; } LocalStateManager.Instance.AddDiagnosis(DiagnosisUid, new DateTimeOffset(DiagnosisTimestamp.Value)); LocalStateManager.Save(); await Xamarin.ExposureNotifications.ExposureNotification.SubmitSelfDiagnosisAsync(); dialog.Hide(); await UserDialogs.Instance.AlertAsync("Diagnosis Submitted", "Complete", "OK"); await GoToAsync(".."); } catch (Exception ex) { Console.WriteLine(ex); dialog.Hide(); UserDialogs.Instance.Alert("Please try again later.", "Failed", "OK"); } finally { IsEnabled = true; } }); } } 

Create ViewModelBase.cs and implement BaseViewModel and set path of navigation.

ViewModelBase.cs

 using System.Threading.Tasks; using MvvmHelpers; using Xamarin.Forms; namespace Mobile.ViewModels { public class ViewModelBase : BaseViewModel{ bool isEnabled; bool navigating; public void NotifyAllProperties() => OnPropertyChanged(null); public bool IsEnabled{ get => isEnabled; set => SetProperty(ref isEnabled, value); } public async Task GoToAsync(string path) { if (navigating) return; navigating = true; await Shell.Current.GoToAsync(path); navigating = false; } } } 

We need to create WelcomeViewModel.cs for implement ViewModelBase and enable notification.

WelcomeViewModel.cs

 async Task Initialize() { IsEnabled = await Xamarin.ExposureNotifications.ExposureNotification.IsEnabledAsync(); if (IsEnabled) await GoToAsync($"//{nameof(InfoPage)}"); } public new bool IsEnabled { get => LocalStateManager.Instance.LastIsEnabled; set{ LocalStateManager.Instance.LastIsEnabled = value; LocalStateManager.Save(); OnPropertyChanged();}} public bool IsWelcomed{ get => LocalStateManager.Instance.IsWelcomed; set{ LocalStateManager.Instance.IsWelcomed = value; LocalStateManager.Save(); OnPropertyChanged(); }} public AsyncCommand EnableCommand => new AsyncCommand(async () => { using var _ = UserDialogs.Instance.Loading(); try { var enabled = await Xamarin.ExposureNotifications.ExposureNotification.IsEnabledAsync(); if (!enabled) await Xamarin.ExposureNotifications.ExposureNotification.StartAsync(); await GoToAsync($"//{nameof(InfoPage)}"); } catch (Exception ex) { Console.WriteLine($"Unable to start notifications: {ex}"); } }); public ICommand GetStartedCommand => new Command(() => IsWelcomed = true); public ICommand NotNowCommand => new Command(() => IsWelcomed = false); } } 

Now, we will create ExposureNotificationHandler.cs for handle notification and save in local state manager and add device verification.

ExposureNotificationHandler.cs

 namespace Mobile { [Xamarin.Forms.Internals.codeserve] public class ExposureNotificationHandler : IExposureNotificationHandler { static readonly HttpClient http = new HttpClient(); public string UserExplanation => "We need to make use of the keys to keep you healthy."; public Task GetConfigurationAsync() => Task.FromResult(new Configuration()); public async Task ExposureDetectedAsync(ExposureDetectionSummary summary, Func>> getExposureInfo) { LocalStateManager.Instance.ExposureSummary = summary; var exposureInfo = await getExposureInfo(); await Device.InvokeOnMainThreadAsync(() => { foreach (var i in exposureInfo) LocalStateManager.Instance.ExposureInformation.Add(i); }); LocalStateManager.Save(); if (LocalStateManager.Instance.EnableNotifications) { var notification = new NotificationRequest { NotificationId = 100, Title = "Possible COVID-19 Exposure", Description = "You may have been exposed to someone who was a confirmed diagnosis of COVID-19.. Tap for more details." }; NotificationCenter.Current.Show(notification); } } public async Task FetchExposureKeyBatchFilesFromServerAsync(Func, Task> submitBatches, CancellationToken cancellationToken) { var rightNow = DateTimeOffset.UtcNow; try { foreach (var serverRegion in AppSettings.Instance.SupportedRegions) { var dirNumber = LocalStateManager.Instance.ServerBatchNumbers[serverRegion] + 1; while (true) { cancellationToken.ThrowIfCancellationRequested(); var (batchNumber, downloadedFiles) = await DownloadBatchAsync(serverRegion, dirNumber, cancellationToken); if (batchNumber == 0) break; if (downloadedFiles.Count > 0) { await submitBatches(downloadedFiles); foreach (var file in downloadedFiles){ try { File.Delete(file); } catch { } } } LocalStateManager.Instance.ServerBatchNumbers[serverRegion] = dirNumber; LocalStateManager.Save(); dirNumber++; } } } catch (Exception ex) { Console.WriteLine(ex); } async Task<(int, List)> DownloadBatchAsync(string region, ulong dirNumber, CancellationToken cancellationToken) { var downloadedFiles = new List(); var batchNumber = 0; while (true) { var url = $"{AppSettings.Instance.BlobStorageUrlBase}/{AppSettings.Instance.BlobStorageContainerNamecodefix}{region.ToLowerInvariant()}/{dirNumber}/{batchNumber + 1}.dat"; var response = await http.GetAsync(url, cancellationToken); if (response.StatusCode == System.Net.HttpStatusCode.NotFound) break; response.EnsureSuccessStatusCode(); if (response.Content.Headers.LastModified.HasValue && response.Content.Headers.LastModified < rightNow.AddDays(-14)) { foreach (var serverRegion in AppSettings.Instance.SupportedRegions) { var dirNumber = LocalStateManager.Instance.ServerBatchNumbers[serverRegion] + 1; while (true) { cancellationToken.ThrowIfCancellationRequested(); var (batchNumber, downloadedFiles) = await DownloadBatchAsync(serverRegion, dirNumber, cancellationToken); if (batchNumber == 0) break; if (downloadedFiles.Count > 0) { await submitBatches(downloadedFiles); foreach (var file in downloadedFiles){ try { File.Delete(file); } catch { } } } LocalStateManager.Instance.ServerBatchNumbers[serverRegion] = dirNumber; LocalStateManager.Save(); dirNumber++; } } } catch (Exception ex) { Console.WriteLine(ex); } async Task<(int, List)> DownloadBatchAsync(string region, ulong dirNumber, CancellationToken cancellationToken) { var downloadedFiles = new List(); var batchNumber = 0; while (true) { var url = $"{AppSettings.Instance.BlobStorageUrlBase}/{AppSettings.Instance.BlobStorageContainerNamecodefix}{region.ToLowerInvariant()}/{dirNumber}/{batchNumber + 1}.dat"; var response = await http.GetAsync(url, cancellationToken); if (response.StatusCode == System.Net.HttpStatusCode.NotFound) break; response.EnsureSuccessStatusCode(); if (response.Content.Headers.LastModified.HasValue && response.Content.Headers.LastModified < rightNow.AddDays(-14)) { var submission = new SelfDiagnosisSubmission(true) { AppPackageName = AppInfo.PackageName, DeviceVerificationPayload = null, Platform = DeviceInfo.Platform.ToString().ToLowerInvariant(), Regions = AppSettings.Instance.SupportedRegions, Keys = keys.ToArray(), VerificationPayload = pendingDiagnosis.DiagnosisUid, }; if (DependencyService.Get() is IDeviceVerifier verifier) submission.DeviceVerificationPayload = await verifier?.VerifyAsync(submission); return submission; } } } } 
 

Planning to Hire Xamarin App Development Company ?

Your Search ends here.

 

For Device verification, we need to add VerifyAsync() method in IDeviceVerifier.cs

IDeviceVerifier.cs

 Task VerifyAsync(SelfDiagnosisSubmission submission); 

We need to create LocalState.cs to store notification, Enable notification and diagnosis data.

LocalState.cs

 using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; using Xamarin.ExposureNotifications; namespace Mobile.Services { public static class LocalStateManager { static LocalState instance; public static LocalState Instance => instance ??= Load() ?? new LocalState(); static LocalState Load() { try { var stateFile = Path.Combine(Xamarin.Essentials.FileSystem.CacheDirectory, "localstate.json"); var data = File.ReadAllText(stateFile); return Newtonsoft.Json.JsonConvert.DeserializeObject(data); } catch { return new LocalState(); } } public static void Save() { var stateFile = Path.Combine(Xamarin.Essentials.FileSystem.CacheDirectory, "localstate.json"); var data = Newtonsoft.Json.JsonConvert.SerializeObject(Instance); File.WriteAllText(stateFile, data); } } public class LocalState { public bool IsWelcomed { get; set; } public bool LastIsEnabled { get; set; } = false; public bool EnableNotifications { get; set; } = true; public Dictionary ServerBatchNumbers { get; set; } = AppSettings.Instance.GetDefaultDefaultBatch(); public ObservableCollection ExposureInformation { get; set; } = new ObservableCollection(); public void AddDiagnosis(string diagnosisUid, DateTimeOffset submissionDate) { var existing = PositiveDiagnoses.Any(d => d.DiagnosisUid.Equals(diagnosisUid, StringComparison.OrdinalIgnoreCase)); if (existing) return; PositiveDiagnoses.RemoveAll(d => !d.Shared); PositiveDiagnoses.Add(new PositiveDiagnosisState { DiagnosisDate = submissionDate, DiagnosisUid = diagnosisUid, }); } public void ClearDiagnosis() => PositiveDiagnoses.Clear(); public PositiveDiagnosisState LatestDiagnosis => PositiveDiagnoses .Where(d => d.Shared) .OrderByDescending(p => p.DiagnosisDate) .FirstOrDefault(); public PositiveDiagnosisState PendingDiagnosis => PositiveDiagnoses .Where(d => !d.Shared) .OrderByDescending(p => p.DiagnosisDate) .FirstOrDefault(); } public class PositiveDiagnosisState { public string DiagnosisUid { get; set; } public DateTimeOffset DiagnosisDate { get; set; } public bool Shared { get; set; } } } 

Here we will create InverterdBoolConverter.cs for value converter.

InvertedBoolConverter.cs

 using System; using System.Globalization; using Xamarin.Forms; namespace Mobile.Converters { public class InvertedBoolConverter : IValueConverter{ public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => value is bool v ? !v : value; public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => value is bool v ? !v : value; } } 

Now add key value and add setter property in App.xaml file.

App.xaml

   #007AFF #5AC8FA #F5F5F5 #98F5F5F5 #80000000 White #007AFF #34C759 #5856D6 #FF9500 #FF2D55 #AF52DE #FF3B30 #5AC8FA #5AC8FA #8E8E93 #AEAEB2 #C7C7CC #D1D1D6 #E5E5EA #F2F2F7 #757575 #757575 #F5F5F5 #FFFFFF Default #FFFFFF White ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
















   

We need to create InitializeBackgroundTask in App.xaml.cs file.

App.xaml.cs

 public App() { InitializeComponent(); Xamarin.ExposureNotifications.ExposureNotification.OverrideNativeImplementation( new Services.TestNativeImplementation()); NotificationCenter.Current.NotificationTapped += OnNotificationTapped; Xamarin.ExposureNotifications.ExposureNotification.Init(); MainPage = new AppShell(); _ = InitializeBackgroundTasks(); } async Task InitializeBackgroundTasks() { if (await Xamarin.ExposureNotifications.ExposureNotification.IsEnabledAsync()) await Xamarin.ExposureNotifications.ExposureNotification.ScheduleFetchAsync(); } void OnNotificationTapped(NotificationTappedEventArgs e) => Shell.Current.GoToAsync($"//{nameof(ExposuresPage)}", false); 

And then we need to create AppShell.xaml file. Open file and add setter property for other page.

AppShell.xaml

   

                              

We require to create route for SharePositiveDiagnosisPage and ExposureDetailsPage. So add in AppShell.xaml.cs file.

AppShell.xaml.cs

 tabDeveloper.IsEnabled = true; Routing.RegisterRoute(nameof(ExposureDetailsPage), typeof(ExposureDetailsPage)); Routing.RegisterRoute(nameof(SharePositiveDiagnosisPage), typeof(SharePositiveDiagnosisPage)); if (LocalStateManager.Instance.LastIsEnabled) GoToAsync($"//{nameof(InfoPage)}", false); 

Now will go with creating SafetyNetAttenstor.cs to implement IDeviceVerifier for verify device.

SafetyNetAttestor.cs

 using System.Threading.Tasks; using Android.Gms.SafetyNet; using Mobile.Droid.Services; using Mobile.Services; using Shared; using Xamarin.Forms; [assembly: Dependency(typeof(SafetyNetAttestor))] namespace Mobile.Droid.Services { partial class SafetyNetAttestor : IDeviceVerifier{ public Task VerifyAsync(SelfDiagnosisSubmission submission){ var nonce = submission.GetAndroidNonce(); return GetSafetyNetAttestationAsync(nonce); } async Task GetSafetyNetAttestationAsync(byte[] nonce) { using var client = SafetyNetClass.GetClient(Android.App.Application.Context); using var response = await client.AttestAsync(nonce, AppSettings.Instance.AndroidSafetyNetApiKey); return response.JwsResult; } } } 

Now, we need to create Dummy Key for share Positive Diagnosis for android and iOS Device.

CreateDummyKeys.cs

 namespace Functions { public class CreateDummyKeys { readonly ExposureNotificationStorage storage; readonly Random random; public CreateDummyKeys(ExposureNotificationStorage storage) { this.storage = storage; random = new Random(); } [FunctionName("CreateDummyKeys")] public async Task Run([HttpTrigger(AuthorizationLevel.Function, "get", Route = "dev/dummy-keys")] Httcodequest req, ILogger logger) { logger.LogInformation("Adding dummy keys..."); var diagnosisUid = random.Next(100001, 128000).ToString(); await storage.AddDiagnosisUidsAsync(new[] { diagnosisUid }); var submission = new SelfDiagnosisSubmission(true) { VerificationPayload = diagnosisUid, Regions = PickRandom(new[] { "ca" }, new[] { "za" }, new[] { "ca", "za" }), AppPackageName = "com.xamarin.exposurenotification.sampleapp", Platform = PickRandom("android", "ios"), Keys = GetKeys() }; await storage.SubmitPositiveDiagnosisAsync(submission); } T PickRandom(params T[] items) { var i = random.Next(items.Length); return items[i]; } List GetKeys() { var now = DateTimeOffset.UtcNow; var keys = new List(); for (var i = 1; i <= 14; i++) { var buffer = new byte[16]; random.NextBytes(buffer); var day = now.AddDays(-1 * i); var dt = new DateTimeOffset(day.Year, day.Month, day.Day, 0, 0, 0, 0, TimeSpan.Zero); keys.Add(new ExposureKey { Key = Convert.ToBase64String(buffer), RollingStart = dt.ToUnixTimeSeconds(), RollingDuration = 144, TransmissionRisk = random.Next(1, 8) }); } return keys; } } } 

Next, create Diagnosis.cs file to manage Diagnosis id like Put, Delete.

DiagnosisUid.cs

 
public class DiagnosisUids { 
readonly ExposureNotificationStorage storage;
 public DiagnosisUids(ExposureNotificationStorage storage) 
{ this.storage = storage; } 
[FunctionName("ManageDiagnosisUids")] 
public async Task Run([HttpTrigger(AuthorizationLevel.Function, "put", "delete", Route = "manage/diagnosis-uids")] Httcodequest req) { var requestBody = await new StreamReader(req.Body).ReadToEndAsync(); if (req.Method.Equals("put", StringComparison.OrdinalIgnoreCase)) { var diagnosisUids = JsonConvert.DeserializeObject>(requestBody); await storage.AddDiagnosisUidsAsync(diagnosisUids); } else if (req.Method.Equals("delete", StringComparison.OrdinalIgnoreCase)) { var diagnosisUids = JsonConvert.DeserializeObject>(requestBody); await storage.RemoveDiagnosisUidsAsync(diagnosisUids); } return new OkResult(); } } 

Home_Page

(Home Page)

Exposure_Notificationr

(Exposure Notification)

 Disable_screen

(Notification Enable or Disable screen)

positive_diagnosis

(Share positive diagnosis)

Conclusion

Apple and Google are building API for a compatible BLE-based contact tracing implementation which relies heavily on generating and storing unique identifiers rolling over a device transmitted to nearby devices. Devices that detect nearby identifiers then store these identifiers as they come into range for up to 14 days. When a person has confirmed diagnosis, they tell their device which then submits locally stored from the last 14 days to backend service provides by the app implementing the API. The device continually requests the key submit by diagnosed people from a backend server. The device then compares these keys to a unique identifier of other devices it has been near in the last 14 days.