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
orandroid.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 withandroid.permission.ACCESS_FINE_LOCATION
in order for Passive Bluetooth reading to succeed if the device is inDOZE
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 BluetoothPeripheralController
s 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
orFragment.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
Updated 8 months ago