Xamarin Android Framework Wrapper

validic_xamarin_android_1.14.5

❗️

Xamarin End of Support May 1, 2024

Microsoft will end support for all Xamarin SDKs including Xamarin.Forms on May 1, 2024.

First, note that your Xamarin apps will remain available in the Apple App Store and Google Play Store after Microsoft's May 1, 2024 support cut-off. Apps won't be removed from the stores, and they will continue working as they have.

However, if you are currently using the Validic Mobile Xamarin wrappers and you need to continue updating and/or using your Xamarin apps, then you should reach out to your Validic Client Success Executive or the Validic Support team immediately for guidance on other options.

You can find additional information on Microsoft's website.

Prerequisites

To get started, download the Xamarin Android framework from the Validic Mobile portal.

In order to use the Validic Mobile Library, you will need a valid organization id and valid user credentials.

If you do not already have an organization id, you will need to first apply for one.

After your organization is set up, you will need a Validic user id and access token.

Validic user can be provisioned using the Validic API, documentation is available for both Legacy API (V1) and Inform API (V2) APIs. The credentials needed are a Validic user ID, an organization ID and a user access token. The user access token value you provide to the SDK is the "user access token" value if you use the Legacy (V1) API. The user access token value you provide to the SDK is the "mobile token" value if you use the Inform (V2) API.

Requirements


This tutorial assumes Xamarin Studio or Visual Studio is installed on the development machine and a new Xamarin Android Project is open. The following must be added to the AndroidManifest.xml and is required to use Validic Mobile:

<uses-sdk android:minSdkVersion="26" />

Installation


The library is divided into 4 separate modules.

Extract the zip file containing the Validic Mobile SDK to a permanent folder

Configure a NuGet repository pointing to the permanent folder called nuget-local by selecting Preferences -> Nuget -> Sources -> Add

Add a new repository by selecting add. Name it anything you like and select the permanent folder the ValidicMobile SDK was extracted to

To add the Validic Mobile SDK to a project double click the packages folder and select the repository created in the previous step. Select the desired packages and click add to add them to the projet’s references

Finally initialize the library within your Application class’s onCreate:

public override void onCreate() {
	base.onCreate();
    Validic.Instance.Initialize(this);
}

or in the Launcher Activity class’s onCreate:

public override void onCreate(Bundle instanceState) {
    base.onCreate();
    Validic.Instance.Initialize(ApplicationContext);
}

Logging

Logging can be enabled using the static methods in the ValidicLog class.

ValidicLog.GlobalLoggingEnabled(true);

By default, nothing is logged. To start logging update the LogLevel to the appropriate level.

ValidicLog.LogLevel = ValidicLog.DEBUG;

or set your own logging instance

ValidicLog.setLogger(new Logger(){
       @Override
       void log(int level, String tag, String msg){
             Timber.log(level, msg);
        }
}

Session


Session is a singleton object and must be accessed by its Instance field. The Session instance stores a User and all pending Record uploads. To use an existing user ID, create a User object and provide it to the StartSessionWithUser(User) method.

var user = new User("Your Organization ID",
                     "A Validic User ID",
                     "A Validic User Access Token");

Session.Instance.StartSessionWithUser(user);

User data is persisted between app launches but is deleted if EndSession() is called.

Session.Instance.EndSession();

Secure Storage

To use encrypted storage and leverage the Jetpack Security module for storing data in the Validic SDK, add the secure-storage module to your application's dependencies```
build.gradle implementation file(secure-storage.aar)

and initialialize the Validic SDK with an instance of the  `EncryptedDataFileSerializer` and a `MasterKey`.```
    ValidicMobile.getInstance().initialize(context, new Configuration.Builder(context)
.serializer(new EncryptedDataFileSerializer(context, 
    new MasterKey.Builder(context, "validic_storage")
    .setKeyScheme(KeyScheme.AES256_GCM)
    .build())
.build())

Data will be encrypted the first time data is saved to disk

Record types

There are seven different subclasses of Record that will be collected by Validic

  • Biometrics – comprised of a user’s biometric health data such as blood pressure, cholesterol, heart rate, and blood and hormone levels.
  • Diabetes – comprised of a user’s blood glucose and hormone levels related to diabetes treatment and management.
  • Fitness – comprised of a user’s activities that are undertaken with the express purpose of exercising. These activities have a defined duration (time, distance, elevation, etc.)
  • Nutrition – comprised of a user’s activities related to calorie intake and consumption and nutritional information (such as fat, protein, carbohydrates, sodium, etc.)
  • Routine – comprised of a user’s activities that occur regularly throughout the day, without the specific goal of exercise, for example calories burned and consumed, steps taken, stairs climbed. These activities are aggregate throughout the day.
  • Sleep – comprised of a user’s measurements related to the length of time spent in various sleep cycles, as well as number of times woken during the night.
  • Weight – comprised of a user’s weight and body mass.

Record Identifiers

The Validic Mobile SDK uses log_id (for the Inform/V2 API) or activity_id (for the Legacy/V1 API) as the unique identifier for a given record. These IDs are created by the mobile SDK before submission to the server.

The Validic server also assigns an id (for the Inform/V2 API) or _id (for the Legacy/V1 API) for each unique record. It is recommended to use the server-side id / _id to identify and/or de-duplicate records post-submission.

Listeners

When Session uploads a record it will notify any stored ISessionListener. To listen for these callbacks, create a class that implements ISessionListener and set the listener on the session. If a 400 error is returned from the server, the record will be discarded and DidFailToSubmitRecord(BaseRecord, Error) will be called. If the record is submitted successfully, DidSubmitRecord(Record) will be called.

public class ISessionListenerImpl : Java.Lang.Object, ISessionListener{
	public DidFailToSubmitRecord(BaseRecord record, Error error){
		//Record processing failed
	}
	public DidSubmitRecord(BaseRecord record){
		// Record was successfully submitted
	}
}
...
ISessionListenerImpl listener = new ISessionListenerImpl();
Session.Instance.SetSessionListener(listener);

Handle events

Session will also fire events for each callback method. Objects can register for callbacks for these events:

Session.Instance.DidSubmitRecord += (session,eventArgs)=>{
	//Record was successfully submitted
}
Session.Instance.DidFailToSubmitRecord += (session, eventArgs)=>{
	//Record was not successfully added to the processing queue
{

Bluetooth


Peripherals

A BluetoothPeripheral represents Bluetooth peripheral models that can be discovered or read from by BluetoothPeripheralController.

The BluetoothPeripheral class contains various properties to be displayed to the user:

  • Name – Returns a String representing the customer recognizable name of the peripheral.
  • ImageUrl – Returns either a local image path (prefixed with “assets://”) or a remote image path to an example peripheral image.
  • PairingInstruction – Returns a String telling the user how to pair the peripheral, if the peripheral requires pairing.
  • Instruction – Returns a String telling the user how to initialize the reading process with the peripheral.
  • ReadingInstruction – Returns a String telling the user what to do during the reading process.

To retrieve a List of supported perpherals simply call:

IList<BluetoothPeripheral> supportedPeripherals = BluetoothPeripheral.SupportedPeripherals();

foreach(BluetoothPeripheral peripheral in supportedPeripherals) {
	Log.V("Test", "Found peripheral: " + peripheral.Name);
}

To retrieve a specific peripheral:

var peripheral = BluetoothPeripheral.GetPeripheralForID(1);

Peripherals are grouped by type such as thermometer, glucose, scale,etc. To retrive an array of peripherals by peripheral type:

IList<BluetoothPeripheral> peripherals = BluetoothPeripheral.GetPeripheralsForType(Peripheral.PeripheralType.GLUCOSE);

Permissions

The following permissions need to be included in the consuming application's mergedAndroidManifest.xml:

  • android.permission.BLUETOOTH
  • android.permission.BLUETOOTH_ADMIN
  • android.permission.FOREGROUND_SERVICE
  • android.permission.ACCESS_COARSE_LOCATION or android.permission.ACCESS_FINE_LOCATION

Android 10

  • Starting with Android 10, android.permission.ACCESS_FINE_LOCATION is required by the OS.
  • android.permission.ACCESS_BACKGROUND_LOCATION must be requested along with android.permission.ACCESS_FINE_LOCATION in order for Passive Bluetooth reading to succeed if the device is in DOZE mode for devices running api 26 or later

Android 11

  • Starting with Android 11, on devices running api >= Android 11 (api 30), the android.permission.ACCESS_BACKGROUND_LOCATION must be granted manually by end user's in the application settings. To navigate to the application settings use the following code
                       Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                                                Uri.parse("package:" + getApplication().getPackageName())); intent.addCategory(Intent.CATEGORY_DEFAULT);                            startActivityForResult(intent, BLE_BACKGROUND_CODE);

Pair

Check if a BluetoothPeripheral requires pairing by calling:

BluetoothPeripheral peripheral = // ...
if(peripheral.RequiresPairing){
	// pair
}

If it does, the user must pair the perpheral before they can take a reading. In order to listen to bluetooth events create a new class that implements IBluetoothPeripheralControllerListener or subclasses SimpleBluetoothperipheralController and override any methods you would like to receive callbacks for.

public class BluetoothPairListener: SimpleBluetoothPeripheralControllerListener
{
    public override void OnFail(BluetoothPeripheralController controller, BluetoothPeripheral peripheral, BluetoothPeripheralController.BluetoothError error)
	{
	    base.OnFail(controller, peripheral, error);
	    // Tell the user to retry they pairing process
	}

    public override void OnSuccess(BluetoothPeripheralController controller, BluetoothPeripheral peripheral)
    {
        base.OnSuccess(controller, peripheral);
	    // The peripheral is now ready to read!
    }

    public override void OnPeripheralDiscovered(BluetoothPeripheralController controller, BluetoothPeripheral peripheral)
    {
        base.OnperipheralDiscovered(controller, peripheral);
	    // The peripheral is currently pairing...
    }
};
BluetoothPeripheral peripheral = // choose a peripheral from the supported perpherals list
BluetoothPeripheralController controller = new BluetoothPeripheralController();
controller.PairPeripheral(peripheral, new BluetoothListener());

String pairingInstructions = peripheral.PairingInstruction;
// Display the pairingInstructions to the user

Read

Once you are ready to read from a peripheral, the reading process is fairly similar to the pairing process. You’ll want to first show the peripheral’s instructions and eventually show the reading instructions once IsReadyToReadFromPeripheral() is called.

public class BluetoothReadingListener: BluetoothPeripheralControllerListener
{
    public override void OnPeripheralDiscovered(BluetoothPeripheralController controller, BluetoothPeripheral peripheral) {
        base.OnPeripheralDiscovered(controller, peripheral);
        String readingInstruction = peripheral.ReadingInstruction;
		// Display the readingInstruction to the user
    }

    public override void OnFail(BluetoothPeripheralController controller, BluetoothPeripheral peripheral, BluetoothPeripheralController.BluetoothError error) {
        base.OnFail(controller, peripheral, error);
        switch (error) {
            case BluetoothError.BluetoothTurnedOff:
                Toast.MakeText(ApplicationContext, "Your bluetooth is off!", ToastLength.SHORT).Show();
                break;
            case BluetoothError.NoUser:
                Toast.MakeText(ApplicationContext, "You have not started the session yet!", ToastLength.LONG).Show();
                break;
            case BluetoothError.Cancelled:
                Toast.MakeText(ApplicationContext, "Bluetooth operation cancelled", ToastLength.LONG).Show();

        }
        Log.E("Error", error.getMessage());
    }

    public override bool OnSuccess(BluetoothPeripheralController controller, BluetoothPeripheral peripheral, IList<Record> records) {

    	// If you want to auto-submit records,
    	// return true;
    	// else if you want to require the user to confirm the record,
    	// return false;

    	// base method returns true;
    	 base.OnSuccess(controller, peripheral, records);
    }
};
BluetoothPeripheral peripheral = // the peripheral you want to read from
string instruction = peripheral.Instruction;
// Display the instruction to the user

BluetoothPeripheralController controller = new BluetoothPeripheralController();
controller.ReadFromPeripheral(peripheral, new BluetoothReadingListener());

Auto-submission of Records

You have the option to automatically submit records as soon as they are captured, or you can wait for user confirmation before submitting the record. For auto submission, return true inside your ShouldSubmitReadingsFromPeripheral method. If you return false you MUST call Session.Instance.SubmitRecord() or the record will be discarded.

Passive Read

The ValidicMobile library supports reading from bluetooth peripherals without any user interaction once a device has been successfully paired. The PassiveManager manages background reads and interactions with any BluetoothPeripheralControllers that are in use. Reading or pairing a peripheral in the foreground will cancel any in progress readings from the background and will restart monitoring in the background once all bluetooth interaction in the foreground has finished.

To set peripherals to be read in the background use the PassiveBluetoothManager.Instance singleton field:

Set<BluetoothPeripheral> peripherals = new HashSet<>();
peripherals.Add(BluetoothPeripheral.GetPeripheralForID(1);
peripherals.Add(BluetoothPeripheral.GetPeripheralForID(2);
peripherals.Add(BluetoothPeripheral.GetPeripheralForID(3);

PassiveBluetoothManager.Instance.PassivePeripherals = peripherals;

To start the service in the foreground. For Android versions > 25 a notification must be supplied to allow the background scanning to continue to run: If the application is targeting at least android-28, the FOREGROUND_SERVICE permission must be added to the application’s manifest as well

<manifest>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
</manifest>

Doze – Documentation regarding Doze

Set<BluetoothPeripheral> peripherals = new HashSet<>();
peripherals.Add(BluetoothPeripheral.GetPeripheralForID(1);
peripherals.Add(BluetoothPeripheral.GetPeripheralForID(2);
peripherals.Add(BluetoothPeripheral.GetPeripheralForID(3);

//Create a notification
Notification notification = ...
NotificationParams params = new NotificationParams(1, notification);

PassiveBluetoothManager.Instance.SetPassivePeripherals(peripherals, params);

To stop monitoring peripherals in the background set the background peripherals to null or an empty set

PassiveBluetoothManager.Instance.PassivePeripherals = null;

To stop monitoring peripherals via the Notification when using a foreground service a PendingIntent should be used to notify a BroadcastReceiver to take the appropriate action:

Register the BroadcastReceiver in the Manifest:

<receiver android:name="com.validic.mobile.ble.BluetoothServiceStopReceiver">
    <intent-filter>
        <action android:name="com.validic.mobile.ble.STOP_BLUETOOTH_SERVICE" />
    </intent-filter>
</receiver>

Add a PendingIntent to be used when the notification is interacted with:

Intent intent = new Intent(BluetoothServiceStopReceiver.ACTION_STOP_BLUETOOTH_SERVICE);
PendingIntent pendingIntent = PendingIntent.GetBroadcast(context, 200, intent, 0);

//Create a notification that uses the pending intent
Notification notification = new NotificationCompat.Builder(context)
                                .AddAction(icon, message, pendingIntent)
                                //continue setting up notification
                                .Build();

Listener

Records will be automatically uploaded as they are read. In order to receive events from the PassiveBluetoothManager, use one of the following two methods. In both cases a PassiveBluetoothManager.IBluetoothListener must be created in order to receive the events:

public class BluetoothListener : Java.Lang.Object, PassiveBluetoothManager.IBluetoothListener
{

    public override void OnSuccess(BluetoothPeripheral peripheral, IList<Record> records) {
        //records received in the background are automatically uploaded
    }

    public override void OnFail(BluetoothPeripheral peripheral, BluetoothPeripheralController.BluetoothError error) {
        //reading failed in the background
    }

    public override void OnPeripheralDiscovered(BluetoothPeripheral peripheral){
        //A peripheral was discovered and we have setup a connection
    }

    public override void OnBackgroundStart() {
        //passive scanning started in the background
    }

    public override void OnBackgroundStop() {
        //passive scanning has stopped in the background either by command or for a foreground reading

};

This listener can either be set on the PassiveBluetoothManager instance:

PassiveBluetoothManager.Instance.SetBackgroundBluetoothListener(listener);

or a PassiveBluetoothReceiver can be registered using the LocalBroadcastManager

var receiver = new PassiveBluetoothReceiver(listener);
LocalBroadcastManager.Instance(context).RegisterReceiver(receiver, PassiveBluetoothReceiver.GetIntentFilter());

Java Interop

BluetoothRequests can be consumed in java clients by converting the request to a JDK CompletableFuture, ListenableFuture or Single depending on which async library is added to the client dependencies

// myApp.csproj

ListenableFuture

        IExecutorService service = Executors.NewSingleThreadExecutor();
        IListenableFuture resultFuture = ValidicBluetoothKt.AsListenableFuture(new ScanRequest(BluetoothPeripheral.GetPeripheralForID(1)));

        try
        {
            resultFuture.AddListener(new Runnable(() => { 
                IListenableFuture resultFuture = ValidicBluetoothKt.AsListenableFuture(new ScanRequest(BluetoothPeripheral.GetPeripheralForID(1)));
                try
                {
                    BluetoothScanResult result = resultFuture.Get() as BluetoothScanResult;
                    Log.i("Bluetooth", result.Device.Address);
                }
                catch (ExecutionException e)
                {
                    e.PrintStackTrace();
                }
            }), service);

        }
        catch (ValidicBluetoothException bluetoothException)
        {
            bluetoothException.PrintStackTrace();
        }

CompletableFuture

        IExecutorService service = Executors.NewSingleThreadExecutor();
        try
        {
            CompletableFuture resultFuture = ValidicBluetoothKt.AsCompletableFuture(new ScanRequest(BluetoothPeripheral.GetPeripheralForID(1)));
        }
        catch (InterruptedException e)
        {
            e.PrintStackTrace();
            resultFuture.HandleAsync((results, throwable)=>{
                if (throwable != null) { Log.e(throwable) }
                else { Log.i("Bluetooth", result.Device.Address); }
            }, service);

        }
        catch (ValidicBluetoothException bluetoothException)
        {
            bluetoothException.PrintStackTrace();
        }

Considerations

There are several situations where passive bluetooth reading is cancelled and resumed at a later time

  • When the system becomes low on memory, passive reading will be cancelled. When the system frees enough memory it will relaunch passive reading and begin scanning again
  • When the end user swipes away the application from the Recent Tasks list, passive reading will be cancelled. 1 Second later, passive reading will automatically restart again.
  • When the end user turns off bluetooth passive reading will be cancelled and will be automatically restarted once bluetooth is turned back on

In all of these cases when passive bluetooth reading is cancelled any current passive readings will be cancelled and the PassiveBluetoothManager.IBluetoothListener will receive onCancelled for each of the peripherals.

Other notes

  • Passive reading is not automatically restarted after device reboot. Your application must be launched or a BootReceiver registered to start passive reading
  • To prevent duplicate records passive reading will not take readings from a peripheral if that peripheral has been read from within the last 15 seconds
  • The PassiveBluetoothManager uses a WeakReference to manage the passive listener. Activities and Fragments may be destroyed and then recreated when coming from the Background into the foreground. It is recommended to have a singleton maintain a strong reference to your listener or to set the listener in Activity.onCreate or Fragment.onAttach

VitalSnap (OCR)


Overview

OCR provides the capability to obtain readings from devices without requiring Bluetooth integration. The recognition process is managed by an instance of ValidicOCRController.

An instance of the ValidicOCRController class is used to manage the recognition process. A ValidicOCRPeripheral represents the peripheral being scanned. The controller is initialized with a peripheral object.

The recognition process involves a fragment that contains the camera preview and an overlay image. The fragment must be injected into your view to operate. The camera capture session ends when the fragment is paused.

Note: If your application’s minimum Android version is set higher than API 21 in your Android manifest, you will need to add android:hardwareAccelerated=“true” to the tag in your Android manifest.

Peripherals

ValidicOCRPeripheral represents peripheral models that can be processed by OCR.

A peripheral object contains various properties which can be displayed to the user:

  • Name - Name of the peripheral comprised of the manufacturer name and model number.
  • ImageURL - URL for an image of the peripheral.

The ValidicOCRController requires a OCRPeripheral for its initializers.

To obtain a OCRPeripheral, you may choose from a list of all available devices:

IList<OCRPeripheral> allSupportedPeripherals = OCRPeripheral.GetSupportedPeripherals();

or if you know the specific peripheral type that you want:

const int ONETOUCH_ULTRAMINI = 1;
OCRPeripheral onetouchPeripheral = // get peripheral OCRPeripheral.getPeripheralForOCRPeripheralType(ONETOUCH_ULTRAMINI);

ValidicOCRActivity

Validic provides an activity to encapsulate the OCRFragment. It will perform the necessary permission checking for Android versions > 23. To start an instance of the activity start by using an intent and Activity.StartForResult();

public void startOCRActivity()
{
	Intent intent = new Intent(this, typeof(ValidicOCRActivity));
	OCRPeripheral peripheral = OCRPeripheral.GetPeripheralForId(1); // one touch
	intent.PutExtra(ValidicOCRActivity.PeripheralId, peripheral.ID);
}

Optionally a File path can added to the request if needed:

File f = new File(FilesDir, "image.png");
intent.PutExtra(ValidicOCRActivity.ImagePath, f.AbsolutePath);

Then start the activity for a result

StartActivityForResult(intent, ValidicOCRActivity.ActionReadOcr);

To receive a converged result in the same activity you launched the ValidicOCRActivity override OnActivityResult()

protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
	if(requestCode ==ValidicOCRActivity.ActionReadOcr && resultCode.Equals(Result.Ok))
	{
		OCRPeripheral peripheral = (OCRPeripheral)data.GetSerializableExtra(ValidicOCRActivity.PeripheralKey);
		BaseRecord record = (BaseRecord)data.GetSerializableExtra(ValidicOCRActivity.RecordKey);
		string s = data.GetStringExtra(ValidicOCRActivity.ImageKey);
		File f = new File(s);
		Uri uri = Uri.FromFile(f);
		if (uri != null)
		{
			bitmap =BitmapFactory
			.DecodeStream(ContentResolver.OpenInputStream(uri));
						//bitmap is now in memory
		}
	}

}

ValidicOCRController

Once a peripheral is obtained, construct the ValidicOCRController and assign it an instance of an IValidicOCRResultListener implementation.

public class ValidicOCRListener: Java.Lang.Object, IValidicOCRResultListener{

    public void DidCompleteReading(BaseRecord record, Bitmap bitmap, ValidicOCRPeripheral peripheral)
    {
	    //Full result was obtained
    }
    public void DidProccessResult(VitalSnapResult result)
    {
	//a partial result was returned
    }
};
ValidicOCRListener listener = new ValidicOCRListener();
ValidicOCRController ocr = ValidicOCRController.InitWithOCRPeripheral(peripheral);
ocr.SetListener(listener);

Runtime unit selection

For any glucose meter in our lineup of supported meters, you can now specify mmol/l or mg/dL at runtime for a given reading. If no unit is provided, mg/dL is assumed.

An example using the ValidicOCRActivity:

OCRPeripheral peripheral = OCRPeripheral.GetPeripheralForId(1); // one touch

Intent intent = new Intent(this, typeof(ValidicOCRActivity));
intent.PutExtra(ValidicOCRActivity.PeripheralId, peripheral.ID);
intent.PutExtra(ValidicOCRActivity.GlucoseUnitKey, Unit.Glucose.Mmoll);

An example using the ValidicOCRController:

ValidicOCRController ocrController = ValidicOCRController.InitWithOCRPeripheral(peripheral, Unit.Glucose.Mmoll);

ValidicOCRFragment

The camera preview and overlay are contained in a fragment that must be injected with an instance of ValidicOCRController. Ensure your view contains a FrameLayout, preferably full screen in size, and give it a referencable id such as activity_ocr_fragment_container.

ocrController.InjectOCRFragment(FragmentManager.BeginTransaction(),
                Resource.Id.activity_ocr_fragment_container)
                .Commit();

This will immediately begin the OCR process.

View Structure

The preview and overlay are handled by the library, but showing intermediate results is not. You must provide a fullscreen container for the fragment in order for the OCR functionality to be reliable. The relevant part of the overlay is guaranteed to be no larger than half the height of the screen. Should you choose to show any other views during the OCR process, please restrict them to the lower half of the screen.

Listener

During OCR processing, methods on an instance of IValidicOCRResultListener will be invoked.

DidProcessResult(VitalSnapResult vitalSnapResult) is invoked for each camera frame captured and provides intermediate results. The VitalSnapResult object contains the current recognized string. The result string can be displayed to the user as an indication of what portion of the display is not being recognized. Depending on the peripheral, the result string may contain linefeeds representing multiple lines being recognized. The result string also indicates whether glare is affecting OCR of a particular digit by returning the * char, and therefore can be helpful feedback for the user to avoid negative effects of glare on the reading.

The result string also indicates whether glare is affecting OCR of a particular digit by returning the * char, and therefore can be helpful feedback for the user to avoid negative effects of glare on the reading.

Log.V(TAG, "Partial result: " + vitalSnapResult.ToString());

DidCompleteReading(BaseRecord record, Bitmap bitmap, OCRPeripheral validicOCRPeripheral) is invoked when OCR processing has completed with sufficient confidence.

if(validicOCRPeripheral.Type == Peripheral.PeripheralType.GlucoseMeter) {
    Diabetes diabetesRecord = (Diabetes) record;
    Log.V(TAG, "Blood glucose captured: " + diabetesRecord.BloodGlucose.ToString());
}

The value received from the listener should be verified by the user and then submitted to the Validic server.

// After verification, queue the record and the image to be uploaded to the server
Session.Instance.SubmitRecord(record, bitmap);

The listener is passed a Record subclass appropriate for the peripheral and the matching cropped preview image with embedded additional metadata. The recognized values returned in the record should be visually validated by the user. The cropped preview image can be displayed to the user to validate the recognized values before uploading to the server.

When the user approves of the values, the record can be uploaded as described in Session.

OCR lifecycle

OCR processing commences when the ocr fragment is injected into your view.

Processing is stopped when the fragment is paused or a final value has been converged. If no value has been converged on, the fragment will resume processing onResume. To restart or to take additional readings with the same peripheral, simply call restartOCR on the ValidicOCRController.

ocrController.RestartOCR();

Samsung Health


Overview

Validic Mobile allows setting subscriptions on Samsung Health Data Types through the SHealthManager. The SHealthManager is a singleton object and should be accessed through the SHealthManager.Instance property. Validic only allows for reading values from the Samsung Health Datastore, it has no writing capability.

General setup

❗️

Approval Required

The Samsung Health integration requires pre-approval for Samsung's Device SDK.

Currently, Samsung is going through an update to better support their partners and, for that reason, they are not accepting applications for the Partner Apps Program. We will keep this section up to date as we hear any updates from the Samsung team.

You can download the aar from the Samsung Health website: https://developer.samsung.com/health/android/overview.html#SDK-Download

Developer Mode

In order to test, you must enable developer mode in the Samsung Health app on your phone. For instructions on how to enable developer mode please see Samsung's website: https://developer.samsung.com/health/android/data/guide/dev-mode.html

Note: Starting with Samsung Health app version 6.11.0.061 (released August 2020), in order to gain access to Samsung Health data, with or without developer mode enabled, you must first be approved by Samsung and receive a Samsung dev access key. Prior versions of the Samsung Health app do not require the dev access key when the Samsung Health app is in developer mode.

You can learn more about requesting access via Samsung's website here: https://developer.samsung.com/health/android/overview.html#Partner-Apps-Program

Note: In order to gain production access to Samsung Health data, without developer mode enabled, you must first be approved by Samsung. You may request access here:

https://developer.samsung.com/health/android/overview.html#Partner-Apps-Program

Adding Samsung Health SDK to project

Add the SHealth jar/aar you have downloaded from Samsung Developer Portal to the project by right clicking on your project, selecting Add>Add Files and select the samsung health jar/aar that you have previously downloaded. It is up to you whether to copy, move, or link to the jar/aar to the project.

Once added, right click on the library to change its Build Action.
For Samsung Health 1.4.0 the .jar's Build Action must be set to AndroidEmbeddedJavaLibrary. For Samsung Health 1.5.0 and forward the aar's Build Action must be set to AndroidAarLibrary

Manage subscriptions

Samsung provides several data types that can be subscribed to for notification when data changes.

The available data types are:

  • com.samsung.health.blood_glucose
  • com.samsung.health.blood_pressure
  • com.samsung.health.body_temperature
  • com.samsung.health.caffeine_intake
  • com.samsung.health.exercise
  • com.samsung.health.nutrition
  • com.samsung.health.hba1c
  • com.samsung.health.heart_rate
  • com.samsung.health.oxygen_saturation
  • com.samsung.health.sleep
  • com.samsung.health.sleep_stage
  • com.samsung.health.uv_exposure
  • com.samsung.health.water_intake
  • com.samsung.health.weight
  • com.samsung.shealth.step_daily_trend

NOTE: Data types that are going to be used in your application must be added to the AndroidManifest.xml.

<application>
    //rest of app

    <meta-data android:name="com.samsung.android.health.platform_type" android:value="rel" />
    <meta-data
        android:name="com.samsung.android.health.permission.read"
        android:value=" com.samsung.health.blood_glucose;
                        com.samsung.health.blood_pressure;
                        com.samsung.health.body_temperature;
                        com.samsung.health.caffeine_intake;
                        com.samsung.health.exercise;
                        com.samsung.health.food_info;
                        com.samsung.health.food_intake;
                        com.samsung.health.hba1c;
                        com.samsung.health.heart_rate;
                        com.samsung.health.oxygen_saturation;
                        com.samsung.health.sleep;
                        com.samsung.health.sleep_stage;
                        com.samsung.health.uv_exposure;
                        com.samsung.health.water_intake;
                        com.samsung.health.weight;
                        com.samsung.shealth.step_daily_trend" />

Subscription Sets

Data types are grouped into logical sets, SHealthSubscription.SubscriptionSet is an Enum that groups several data types together to register for related data types. The available subscription sets are:

  • Routine - Daily step data
    • com.samsung.shealth.step_daily_trend
  • Diabetes - Blood glucose and hba1c data
    • com.samsung.health.blood_glucose
    • com.samsung.health.hba1c
  • Weight - Weight and height data
    • com.samsung.health.weight
  • Fitness - Exercise data
    • com.samsung.health.exercise
  • Sleep - Sleep and sleep stage data
    • com.samsung.health.sleep
    • com.samsung.health.sleep_stage
  • Nutrition - Food and nutrition data
    • com.samsung.health.food_info
    • com.samsung.health.food_intake
    • com.samsung.health.caffeine_intake
    • com.samsung.health.water_intake
  • Blood Pressure - Blood pressure data
    • com.samsung.health.blood_pressure

The group of data types for each subscription set can be found using:

    SHealthSubscription.PermissionStringsForSubscriptionSet(SHealthSubscription.SHealthSubscriptionSet.FITNESS);

Subscribe to data types

Subscribing to Samsung Health updates only needs to be done once for a user. The subscriptions will be persisted across app launches in the Session object.

Data types can be subscribed to individually:

    SHealthManager.Instance.AddSubscriptionsForDataTypes("com.samsung.health.blood_glucose", "com.samsung.health.blood_pressure");

or as part of a subscription set:

     SHealthManager.Instance.AddSubscriptionsForDataTypes(SHealthSubscription.PermissionStringsForSubscriptionSet(SHealthSubscription.SHealthSubscriptionSet.FITNESS));

To start the Samsung Health service in the foreground. For Android versions > 25 a notification must be supplied to allow the background scanning to continue to run:

If the application is targeting at least android-28, the FOREGROUND_SERVICE permission must be added to the application’s manifest as well

<manifest>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
</manifest>

Doze – Documentation regarding Doze

Individual subscription:

    //Create a notification
    Notification notification = ...
    NotificationParams params = new NotificationParams(1, notification);

    SHealthManager.Instance.AddSubscriptionsForDataTypes(param, "com.samsung.health.blood_glucose", "com.samsung.health.blood_pressure");

As a subscriptions set:

    //Create a notification
    Notification notification = ...
    NotificationParams params = new NotificationParams(1, notification);

    SHealthManager.Instance
        .AddSubscriptionsForDataTypes(
                params, 
                SHealthSubscription.PermissionStringsForSubscriptionSet(SHealthSubscription.SHealthSubscriptionSet.FITNESS)
        );

To stop monitoring peripherals via the Notification when using a foreground service a PendingIntent should be used to notify a BroadcastReceiver to take the appropriate action:

Register the BroadcastReceiver in the Manifest:

<receiver android:name="com.validic.mobile.shealth.SHealthServiceStopReceiver">
    <intent-filter>
        <action android:name="com.validic.mobile.shealth.STOP_SHEALTH_SERVICE" />
    </intent-filter>
</receiver>

Add a PendingIntent to be used when the notification is interacted with:

Intent intent = new Intent(SHealthServiceStopReceiver.ACTION_STOP_SHEALTH_SERVICE);
PendingIntent pendingIntent = PendingIntent.GetBroadcast(context, 200, intent, 0);

//Create a notification that uses the pending intent
Notification notification = new NotificationCompat.Builder(context)
                                .AddAction(icon, message, pendingIntent)
                                //continue setting up notification
                                .Build();

Fetch historical data

Validic provides the ability to query 6 months of data for a subset of data types provided by Samsung Health by querying a group of ValidicMobile.SHealth.SHealthHistoricalSet

Currently only 2 historical sets are available

  • ValidicMobile.SHealth.HistoricalSet.FITNESS - Exercise data
  • ValidicMobile.SHealth.HistoricalSet.ROUTINE - Step data

Simply call the method on the SHealthManager instance to perform the operation

var historicalSet = new Java.Util.ArrayList(); historicalSet.Add(SHealthSubscription.SHealthHistoricalSet.Routine);
historicalSet.Add(SHealthSubscription.SHealthHistoricalSet.Fitness);

var today = Java.Util.Calendar.Instance;
var from = Java.Util.Calendar.Instance;
from.Add(Java.Util.CalendarField.DayOfMonth, -180);

SHealthManager.Instance.FetchHistoricalSets(historicalSet, from, today);

Listener

Operations performed on Samsung Health are asynchronous. To listen for events a listener can be placed on the SHealthManager.Instance.

public class MySHealthListener: Java.Lang.Object, ISHealthListener
{
    public void onPermissionChange(string[] accepted, string[] denied)
    {
        // Permissions have changed. Track them from  here
    }
    public void onError(SHealthError error)
    {
        //Handle the error type here
    }
    public void OnRecords(<BaseRecord.RecordType, Java.Lang.Integer> summary){
        //Data has changed for one of the the subscriptions and records were gathered
        //Dictionary is keyed by the record type that was gathered and reports how many of each record type were changed.
    }

    public void OnHistoricalFetch(<BaseRecord.RecordType, Java.Lang.Integer>) {
        //Data has been returned from a historical data fetch
        //Dictionary is keyed by the record type that was gathered and reports how many of each record type were changed.
    }

}
ISHealthListener listener = new MySHealthListener();
SHealthManager.Instance.SetListener(listener);

Notes

In order to resume listening for subscriptions upon app launch you must call ObserveCurrentSubscriptions() inside your application’s onCreate function:

public class YourApplication : Application {

    public override void OnCreate() {

        base.OnCreate();

        Validic.Instance.Initialize(context);
        SHealthManager.Instance.ObserveCurrentSubscriptions();

    }

}

Calling Session.Instance.EndSession() will remove all Samsung Health subscriptions and stop listening for new data.

The SHealthManager uses a WeakReference to manage the passive listener. Activities and Fragments may be destroyed and then recreated when coming from the Background into the foreground. It is recommended to have a singleton maintain a strong reference to your listener or to set the listener in Activity.onCreate or Fragment.onAttach