Creating an Android application that users never see

Alfred Chan
SCTD, GovTech
Published in
8 min readMar 1, 2024

“What?” might be your first thought when you read the title. When I joined GovTech’s Smart City Technology Division (SCTD) team back in Nov 2022, I was tasked to work on a background service application called Sensei Android Gateway. Another team developed a front-facing application using React Native. My end goal for this project was to work with the other team to merge both applications into a single application for the user after multiple months of trials.

So… What does Sensei Android Gateway do?

Image of Omron’s Blood Pressure Monitor and Sensei Android Gateway

The Sensei Android Gateway application consists of 2 parts: the first part is the main feature for users where a background service runs constantly to communicate with health monitoring devices such as a blood pressure monitor or weighing scale via Bluetooth Low Energy (BLE) to retrieve essential health data in real-time. That data is then sent via API calls to our backend and later appear as notifications in the front-facing app for the user to see.

By utilising a background service, it empowers the users to seamlessly take measurements without the need for them to open the app. This would significantly simplify their experience, particularly for users who are not tech-savvy.

The second part is an admin-only page for us to onboard the users and pair the health monitoring devices to get the application started.

Device Communications with Bluetooth Low Energy (BLE)

Image of Oximeter, A&D’s Blood Pressure Monitor and early version of Sensei Android Gateway

BLE, the main feature of the application, was the first thing we started to develop. We were experimenting with different brands (A&D and Omron) and different types of sensors like blood pressure monitors, weighing scales and oximeters. Our applications were required to do the following:

  1. Pair/unpair a sensor device
  2. Establish communication using BLE and retrieve the required data
  3. BLE communication needs to happen even when the application is not opened

We decided to work with Omron’s blood pressure monitor and weighing scale but we were required to use Omron’s Software Development Kit (SDK) that includes their pre-built functionality to communicate with their devices. We were also given an sample Android application in Java with documentation but their sample app only works if the application has a screen for the user. Here, there were two concerns:

  1. The BLE connection process needs to happen automatically since our application works only in the background. To solve this, we had to convert the sample code from Java to Kotlin and edit the code to use the Omron SDK to run the necessary method for automatic pairing and transferring data.
  2. In the future, in order to integrate with other brands of devices, we cannot fully rely on Omron SDK. To solve this, we decided to use our own BLE scan logic to filter and detect the devices that we are interested in before running Omron SDK’s method to pair or transfer data. This allows the application to call for other ways of connecting if other brands require other SDK, etc.

After we were done with the above we finally had a proper first working version of the application but that wasn’t all we had to do.

How can the application communicate with the devices at anytime?

BLE scans have a few constraints. They can scan continuously for a maximum of 30 minutes and a scan cannot be started and stopped more than 5 times within 30 seconds. If these limitations are not observed, the BLE scanner will crash quietly. (Yes, there is no callback or other ways to detect this crash as of now)

Another concern is that, the devices only advertised when a measurement is taken. This means that the application has to wait for the device’s advertisement which may happen at anytime as we have no idea when a user would start using their blood pressure monitor or weighing scale.

The solutions that we have came up with is to make the BLE scan work 24/7 so that we can detect the advertisement at any time. We created a service to restart the scan every 10 minutes in order to “reset” the 30 minutes timer that the BLE scan had.

In exchange for an increase in battery consumption, this would ensure that the user’s measurement would come in as soon as possible. In addition, for our project’s use case, the application will only be used at home so the time it takes to get the measurement would be of a higher priority than battery life.

Foreground Service or Background Service?

When it comes to long-running operations like our BLE scan which is required to be running 24/7, we need to use something called a service to perform those tasks. Services exist in two main variants: Foreground services and background services, each with distinct behaviours and use cases.

Foreground services are used when the tasks to be done are of higher priority with access to system resources and stays alive even when the application is closed. It is usually used with tasks that needs user awareness that it is running in the background like music player.

Background services are used when the tasks to be done are of lower priority with limited system resources and can be killed by Android OS if resources are low. It is usually used with tasks that can be run silently without disturbing the user like syncing data.

Although foreground services drain more battery life, these are important for our use case so the service keeps running and won’t be killed by the Android OS silently.

Foreground service doesn’t continue after app update and device reboot

This is another interesting issue I found after testing. The BLE scan did not seem to work after updating the app and after restarting the Android device. That’s when I found out in Android, we need to register broadcast receivers to detect the two different Intents below.

Intent.ACTION_BOOT_COMPLETED //To detect restarting of device
Intent.ACTION_MY_PACKAGE_REPLACED //To detect app updates

Once the receivers detects any of the Intents above, we can run the process to start the services and BLE scan again.

Deployment Process

Image of the Sensei Care Kit given to user in their house

Users who signed up for the trial were given a Sensei Care Kit that includes an Android tablet, blood pressure monitor and weighing scale. Our application will be deployed through Samsung Knox which is a type of Mobile Device Management Solution (MDM). The tablet given is locked in Kiosk mode during the trial period and contains both the front-facing application and Sensei Android Gateway only.

With help from Samsung, we were able to set up the MDM with a tablet, deploy/update an application, and monitor its health through MDM.

This helped us clarify the requirements on each tablet to give the best possible user experiences. For example, there are 2 types of Kiosk mode, Single-app and Multi-app Kiosk. We required Single-app Kiosks in our use case as this would lock the tablet to display only the front-facing app while Sensei Android Gateway will be running in the background. Since most of our user is not tech-savvy, we selected this way in order to make the system as simple as possible.

Battery Optimisation

Battery optimisation is a feature in Android that lets users set limits on app background activity. Depending on the option selected, it will affect the app functionality in exchange for better battery life.

After deploying our application to the users, we found out that Bluetooth was working intermittently and communication between the sensors and the application disconnected after some time. To make things harder, the issue only happened for some but not all users. In order to find the root cause, we had to try different options such as placing more demo sets in the office to let it run for days, go to the user’s place to assist in fixing the issue to better understand what is happening, etc.

That’s when we found out that our application’s battery optimisation was set to “Optimised”. In Android, when an application isn’t used for a long period of time, it puts the application into deep sleep to save battery and at the same time causes the application to not work until it is opened again. In order to prevent this from happening, we needed to change the battery optimisation from “Optimised” to “Unrestricted”.

While the setting can be changed through Samsung Knox, it is not straightforward. In order for the new setting to take effect, we needed to open the Sensei Android Gateway in the foreground after changing the setting to “Unrestricted”. Next, the front-facing app needed to be brought to the foreground again to reinstate the Kiosk mode operations. In addition, we found out this can only be done by firing the command to one tablet at a time. With the help of our team members, we managed to orchestrate this series of changes remotely with minimal disruptions to the users and we were able to see an improvement in Bluetooth connectivity after that.

Merging Sensei Android Gateway and the front-facing app

First version of Sensei Android Gateway in the Integrated Application

After conducting trials at several different locations, our team started work to merge the Sensei Android Gateway and the front-facing app into one single application.

The front-facing app was developed using React Native while Sensei Android Gateway was developed using Kotlin. At first, there were definitely some concerns as we were still new to React Native and it was not clear how many features from Sensei Android Gateway we can practically port over to a React Native application.

To my surprise, while there were a few additional steps to set up, we could actually move our whole code in Kotlin into a React Native application! To make a clean separation, we decided that user interface code will be handled in React Native and the logic code will be handled in Kotlin.

In order for the above idea of clean separation to work we needed to address a few concerns:

  1. To call a logic from Kotlin (E.g. API call), I need to be able to call a Kotlin method from React Native. For this, we need to set up native modules as a “middle-man” to handle communication between React Native and Kotlin. These native modules would contain methods in Kotlin and are ready to be called from React Native at any time.
  2. To handle user interface update for any BLE related communication, React Native needs to have a receiver to receive updates from Kotlin. For this, we need to set up something called a Native Event Emitter. BLE related communications are handled in Kotlin and we need to update the user in some cases like the device has been paired successfully. To do this, we need to set up a receiver in React Native to listen when needed and we can send an event from Kotlin.

Conclusion

In conclusion, this has been a great and interesting 1+ year project that I have been working on. I got to learnt much more about BLE, improve more in Kotlin and learn React Native. We also got to assist in the user onboarding process and communicate with real users about we have developed which was a very interesting experience.

Thank you to everyone in the team and it has really been a great 1+ year. Thanks for reading till the end!

--

--