×

iFour Logo

Exposure Notification API Support for Xamarin Apps

Kapil Panchal - June 09, 2021

Listening is fun too.

Straighten your back and cherish with coffee - PLAY !

  • play
  • pause
  • pause
Exposure Notification API Support for Xamarin Apps

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.

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.


                       
				

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<(int, list=""><(int, list=""> VerifyAsync(SelfDiagnosisSubmission submission); 
				

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

LocalState.cs

<(int, list=""><(int, list="">
 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

<(int, list=""><(int, list="">
 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

<(int, list=""><(int, list="">
              #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

            <(int, list=""><(int, list="">
 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

            <(int, list=""><(int, list="">
              
                              
				

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

AppShell.xaml.cs

            <(int, list=""><(int, list="">
 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

            <(int, list=""><(int, list="">
 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

            <(int, list=""><(int, list="">
 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="">
				

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

DiagnosisUid.cs

            <= 14=""><(int, list=""><(int, list="">
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)
Notification Enable or Disable screen
(Notification Enable or Disable screen)
Exposure Notification
(Exposure Notification)
Share 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 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 1. How Exposure Notification Works? 2. Conclusion 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 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. 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 Label FontAttributes="Bold" FontSize="Subtitle" Style="{DynamicResource SubtitleLabelStyle}" Text="Diagnosis Shared" /> 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. Read More: Oauth Login Authentication With Identity Provider In Xamarin.forms 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)> 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 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)> 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 () is IDeviceVerifier verifier) submission.DeviceVerificationPayload = await verifier?.VerifyAsync(submission); return submission; } } } } Planning to Hire Xamarin App Development Company ? Your Search ends here. See 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 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) (Notification Enable or Disable screen) (Exposure Notification) (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.

Build Your Agile Team

Enter your e-mail address Please enter valid e-mail

Categories

Ensure your sustainable growth with our team

Talk to our experts
Sustainable
Sustainable
 

Blog Our insights

Power Apps vs Power Automate: When to Use What?
Power Apps vs Power Automate: When to Use What?

I often see people asking questions like “Is Power App the same as Power Automate?”. “Are they interchangeable or have their own purpose?”. We first need to clear up this confusion...

Azure DevOps Pipeline Deployment for Competitive Business: The Winning Formula
Azure DevOps Pipeline Deployment for Competitive Business: The Winning Formula

We always hear about how important it is to be competitive and stand out in the market. But as an entrepreneur, how would you truly set your business apart? Is there any way to do...

React 18 Vs React 19: Key Differences To Know For 2024
React 18 Vs React 19: Key Differences To Know For 2024

Ever wondered how a simple technology can spark a revolution in the IT business? Just look at React.js - a leading Front-end JS library released in 2013, has made it possible. Praised for its seamless features, React.js has altered the way of bespoke app development with its latest versions released periodically. React.js is known for building interactive user interfaces and has been evolving rapidly to meet the demands of modern web development. Thus, businesses lean to hire dedicated React.js developers for their projects. React.js 19 is the latest version released and people are loving its amazing features impelling them for its adoption.