Android UI Testing in the cloud with Genymotion Cloud SaaS

This is a follow up to a previous unfinished post:

The immediate suggestion most people will have for running UI tests in the cloud is Firebase Test Lab. Firebase Test Lab is a great solution, but I need something that has the following capabilities:

  • The ability to install multiple arbitrary APK’s that are not the app under test
  • The ability to clone customized emulators
  • The ability to save and resume emulators across test runs

Why do I need these capabilities?

I am developing an Android application that runs on and integrates with a Point of Sale platform made by Clover. Specifically, I need to test my application in a test environment that mimics the Clover Station 2018 (pictured below)

Clover provides APK’s that allow you to re-create the operating environment of the Station 2018 on an emulator.

Using Genymotion Cloud we can re-create the Station 2018 environment on a tablet emulator image and then save that emulator image and use it or clone it later. This is something not available on Firebase Test Lab.

Below is a screenshot of a tablet emulator image configured as a Clover Station 2018 running in Genymotion Cloud SaaS.

Once we have configured the emulator, we can save the state of the emulator and give it a meaningful name.

The recipe UUID allows us to start a new instance of this emulator image using the recipe UUID. Genymotion provides a CLI tool that allows us to start an instance from the command line using the following command:

gmsaas instances start c9246a83-4f38-4742-a11f-42b5b765dbdc instanceToTestWith

That will start an instance that we can connect to over ABD. We can also connect to the display through the Genymotion Cloud console.

The CLI tool also provides a command to connect ADB to the instance that was just started. After connecting over ADB, we can see that the Clover packages are installed and the emulator is a perfect clone of the image we previously prepared:

Darrans-MBP:cef darrankelinske$ gmsaas instances start c9246a83-4f38-4742-a11f-42b5b765dbdc instanceToTestWith
Darrans-MBP:cef darrankelinske$ gmsaas instances adbconnect 9f8e32ea-543c-4d64-8609-b016165a6754
Darrans-MBP:cef darrankelinske$ adb devices
List of devices attached
localhost:55813	device

Darrans-MBP:cef darrankelinske$ adb shell
vbox86p:/ # pm list packages -3                                                                                                            
vbox86p:/ # 

Full command reference:

Genymotion also provides a CircleCI orb which makes integrating the emulator into a CircleCI build process straightforward.

Using the orb we can make a job that runs UI tests on an instance of the emulator image we previously created. The job outlined below will build APKs to test, start a Genymotion emulator instance that is pre-configured as a Clover Station 2018 and then run a specific set of UI tests. Once the test are complete, the emulator instance will be stopped.

    executor: android
      - checkout
      - run:
          name: Chmod permissions #if permission for Gradlew Dependencies fail, use this.
          command: |
            sudo chmod +x gradlew
      - restore_cache:
            - gradle-{{ checksum "app/build.gradle" }}
      - run:
          name: Build APKs to test
          command: |
            ./gradlew aCD aCDAT
      - genymotion-saas/setup
      - genymotion-saas/start-instance:
          recipe_uuid: "c9246a83-4f38-4742-a11f-42b5b765dbdc"
      - run:
          name: Install APKs to test
          command: |
            ./gradlew iCD iCDAT
      - run: (! adb shell am instrument -w -r  --no-window-animation  -e debug false -e class '' com.fivestars.mpos.clover.test/androidx.test.runner.AndroidJUnitRunner | tee /dev/tty | grep -q FAILURES!!!)
      - genymotion-saas/stop-instance

And with that, we have accomplished what we needed. We are now able to test our application in CI using an emulator that is pre-configured with the environment we need to test in.

Thank you for reading! Please let me know what you think!

Genymotion offers a free hour of Cloud SaaS.

There is an introduction video on how to get started below. Give it a try!


Getting started with Java CEF on Linux

This is a brief tutorial on how to get started with Java CEF on Linux. The primary repository for Java CEF is here:

Without delay, here are the steps to get Java CEF running on Linux64

Download the latest Linux 64 release from here:

Unzip the archive you downloaded

Make sure $JAVA_HOME is set in terminal.


Make sure $LD_LIBRARY_PATH is set in terminal.


If it is not set, set it.

export LD_LIBRARY_PATH=/usr/lib/jvm/java-11-openjdk-amd64/lib

Now, we should be able to run the app.

In the directory of the unzipped archive cd to the java-cef-build-bin directory

Execute the sample app by using the following command ./

You should see an app like the one in the screenshot below


Running x86 Android Emulators in the Cloud (Incomplete)

The problem

I was recently presented with the problem of testing a system that required two android emulators running side by side in coordination. Our QA team had set this up to work on a local windows machine, but we were exploring alternatives and desired moving our testing activities to the cloud.


We started out exploring CircleCI. This is a familiar Cloud CI tool, but they do not support emulators. We also explored using their MacOS variants, which appear to be hosted on ESX, but I ran into GPU driver issues.

EC2 instances did not have any virtualization extensions exposed to the VM. This left us with only being able to use ARM emulation which is extremely slow and would probably take us an entire day or more to run our entire test suite.

After reading the docs on how to configure hardware acceleration for the Android Emulator, I realized that we needed to have access to virtualization extensions inside our VM/container. In virtualization speak, we needed “nested virtualization.” Both Google Cloud and Azure support nested virtualization, but I am more familiar with Google Cloud Platform so I started out on GCP.

The solution

A virtual machine hosted inside GCP that supports nested virtualization. We are going to setup our solution on Debian, but I have also had it working on Ubuntu. Let’s get started.

To begin, we will follow the instructions defined here

Create a disk to use:

gcloud compute disks create disk1 --size=30gb --image-project ubuntu-os-cloud --image-family ubuntu-1604-lts  --zone us-central1-b

Create an image from the disk and enable nested virtualization

gcloud compute images create android-emulator-image \
  --source-disk disk1 --source-disk-zone us-central1-b \
  --licenses ""

Create an instance from the image. It needs to be at least Haswell and we are using a machine-type that has a few extra cores which will help us run both emulators at the same time.

gcloud compute instances create android-emulator-vm --zone us-central1-b \
	      --machine-type n1-standard-4 \
              --min-cpu-platform "Intel Haswell" \
              --image android-emulator-image

Let’s SSH to the instance and make sure that we have virtualization available

gcloud compute ssh android-emulator-vm

#! Inside VM
grep -cw vmx /proc/cpuinfo

You should get a non zero value to indicate that nested virtualization is enabled.

Now, let’s configure our VM so that we can connect to it using VNC and use virtualization.

sudo apt-get update -y

sudo apt-get install xfce4 qemu-kvm tightvncserver iceweasel -y

We can now start our VNC server using the following command. We will be prompted for a password. Enter in a password and remember it.


Once we start the server it will create a few configuration files for us. We need to stop our VNC server nad update our config files so that we get a more usable display when we connect to our VNC server.

vncserver -kill :1
vi ~/.vnc/xstartup

Inside xstartup, enter in the following details:

startxfce4 &

[ -x /etc/vnc/xstartup ] && exec /etc/vnc/xstartup
[ -r $HOME/.Xresources ] && xrdb $HOME/.Xresources
xsetroot -solid grey
vncconfig -iconic &

Let’s startup our VNC server with the new config and connect to it.


From Apache Cordova to Kotlin Multiplatform


In this post we will explore an alternative to Apache Cordova that utilizes Kotlin Multiplatform. This example will show us how we can add Bluetooth functionality to a web app hosted in an Android WebView. This solution provides strong typing on both the JavaScript and Android platform and has the benefit of being able to share types across platforms too!

This solution is only implemented to replace the Android implementation, but could be extended to iOS or even Chrome Embedded Framework.

Throughout this post, we will be looking at the following repository and branch:

A Brief Overview of Cordova

Cordova is a cross platform tool that allows you to host your application UI in a web container, a WebView on Android, and interact with native API’s using a JavaScript bridge. Apache Cordova is well proven, but developing plugins which extend the capabilities of Cordova apps is an error-prone activity and burdensome. While attending Android Dev Summit 2019, I asked the WebView engineers what they would suggest to bridge web and native and they suggested using WebMessage. I was skeptical at first, but I was inspired after seeing this answer on StackOverflow.

The Kotlin Multiplatform Alternative

To replace Cordova we need to be able to do the following:

1. Send messages to Native Android from JavaScript

2. Take action from message received from JavaScript

3. Respond with Success, Failure, and possibly include a data payload after receiving a message from JavaScript

1. Send Messages to Native Android from JavaScript

To send message to Native Android from JavaScript, we need to setup a bridge that allows two-way communication. This bridge was inspired by the previously mentioned SO answer.

The first step is to create a WebMessageChannel for our webview

private val webMessagePorts = WebViewCompat.createWebMessageChannel(webView)

This will create a message channel that will allow Android to talk to JavaScript and will allow JavaScript to talk to Android. We get two ports back from the call to this method and we need to send one of the ports back to JavaScript so that it knows how to talk to us. The following snippet sets a native Android callback that will receive the messages that come from JavaScript and then sends one of the ports of the message channel to the WebView.

val destPort = arrayOf(webMessagePorts[1])

// Set callback for port - This is what will receive the message that are sent from JavaScript

// Post a message to the webview. The JavasScript code will have to capture this port so that it can talk to Native Android
WebViewCompat.postWebMessage(webView, WebMessageCompat(KEY_CAPTURE_PORT, destPort), Uri.EMPTY)

Now let’s look at how the JavaScript side handles the first incoming message from Native Android. This is where things get awesome. We will be writing “JavaScript” using Kotlin by utilizing Kotlin/JS. The configureChannel function below is called when our web app is first loaded and does the following:

  1. Listens for incoming messages
  2. When it gets a message, it checks to see if the data is the key we sent from native capturePort
  3. If it is the capturePort message, then we assign the port to outputPort so we can send messages to native Android.
    fun configureChannel() {
        console.log("Configuring channel")
        window.addEventListener("message", {

            val event = it as MessageEvent

            if ( != KEY_CAPTURE_PORT) {
                console.log(" ${}")
            } else if ( == KEY_CAPTURE_PORT) {
                console.log("assigning captured port")
                outputPort = event.ports[0]
        }, false)


We are writing our web app in Kotlin too. Below we call configure channel from inside our index.html file and show a glimpse of adding a button that allows us to connect to a specific device over Bluetooth.


    val root = document.getElementById("root")

    root?.append {

        div {
            button {
                text("Connect to Device")
                onClickFunction = {
                    BluetoothSerial.connect("18:21:95:5A:A3:80", {
                        console.log("Success function in connect");
                    }, {
                        console.log("Not success");

This is what it looks like rendered on a tablet. It’s a POC and is focused on function so please forgive the design language ;P

Now that we have our channel setup, we can send messages to Native Android from JavaScript. In the snippet above, we are initiating a Bluetooth connection to another device. Let’s look at how this is implemented using the power of Kotlin Multiplatform and sharing code between Android and JavaScript.

Our messages sent from JavaScript will contain the following model:

data class JavascriptMessage(
    val action: Action,
    val successCallback: Callback?,
    val failureCallback: Callback?,
    val data: Map<String, String>? = null

Our messages coming from JavaScript include an Action. An example is CONNECT. Which informs Android that we would like to initiate a Bluetooth connection. The message also includes a optional success and failure callbacks. These are invoked based on what happens on the native side. Finally, the message includes data property that allows us to specify data that is delivered with the Action. In a CONNECT scenario we include a KEY_MAC_ADDRESS property that specifies which device to connect to. These keys are defined in common code and shared across platforms.

Now we need to register some callbacks to handle the response from the Native side and then send the message over.

    fun connect(macAddress: String, onSuccess: () -> Unit, onFailure: () -> Unit) {

        val message =
                Action.CONNECT, Callback.CONNECT_SUCCESS, Callback.CONNECT_FAILURE, mapOf(
                    KEY_MAC_ADDRESS to macAddress


We are serializing using to stringify objects before sending them across the bridge.

We can even unit test the bridge

    fun testListenSendsListenAction() {
        var message: JavascriptMessage? = null
        BluetoothSerial.messageHandler = object :MessageHandler {
            override fun sendMessageToNative(javascriptMessage: JavascriptMessage) {
                message = javascriptMessage
        BluetoothSerial.listen({}, {})
        assertEquals(Action.LISTEN, message!!.action)

2. Take action from message received from JavaScript

When we set up our web message channel, we assigned a message handler for incoming messages. This same message handler is where we deserialize incoming JavaScriptMessage payloads and determine what to do based on the Action we find. We then grab the relevant data associated with the Action. In this instance we are looking for the MAC address to connect to.

                val javascriptMessage = json.parse(JavascriptMessage.serializer(),!!)

                when (javascriptMessage.action) {
                    Action.CONNECT -> BluetoothSerial.connect(
              !![KEY_MAC_ADDRESS] as String,

Now we can call a native Android function that will assign the success and failure callbacks that were passed in from the JavascriptMessage and attempt connecting to the device!

    fun connect(
        macAddress: String,
        successCallback: Callback?,
        failureCallback: Callback?
    ) {
        val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
        val device = bluetoothAdapter.getRemoteDevice(macAddress)
        if (device != null) {

            successCallback?.run {
                val nativeDataMessage =
        } else {

3. Respond with Success, Failure, and possibly include a data payload

If we are successful, we call the success Callback that was passed in and respond with a NativeDataMessage. A native data message includes the callback that we are replying to and any relevant data. Because this is a string, we can send back any type that can be serialized to a string.

class NativeDataMessage(val callback: Callback, val data: String?)

If we have a failure, then we send a NativeDataMessage back to the failure callback:

    private fun sendFailure(
        failureCallback: Callback?,
        e: Exception? = null
    ) {
        failureCallback?.run {
            val nativeDataMessage =


We now have a facility to initiate actions from JavaScript to Native and back and can build upon that. We have implemented these Action commands and are still iterating on this pattern and example.

enum class Action {

The callback handling on both sides is still be iterated and improved to be more flexible. In another implementation we are using callbacks that receive parameters of custom types (think User or Product) instead of just String.

Another improvement would be to move all of the Android code that is related to the Bluetooth “plugin” into the Android source set of the SharedCode module. This would allow us to ship this solution as Kotlin multiplatform library 🙂

Video Walkthrough

Thank you!

Please let me know what you think and any suggestions you might have. Thank you!


Charts for Flutter and Flutter Web

Many apps have a need to display some sort of data in chart form. Recently, I was tasked with implementing a chart solution on Flutter and Flutter Web and wanted to walk you through the implementation.

In this example, we are going to use the charts_flutter package.

To get started we need to add it as a dependency in our pubspec.yaml

  charts_flutter: ^0.8.1

This package has a great example gallery that includes code snippets that show how to implement each example. In this post we are going to walk through a time series chart implementation.

The end result will be an app that displays a chart on Flutter Mobile and Web.

Once we have imported our dependency, we need to get the data from our data source and transform it into a model that the chart library knows how to use. To do this, we are going to start with the repository pattern. We will create a ReportRepository class and fetch our reports from it. The data we are using comes from a sensor device and the sensor devices provides us with vibration and trip count data.

Each report entry will look like this:

    "date": "2020-01-15T19:00:00-0500",
    "trips": 553,
    "vibration": 1.08154506437768,
    "doors": 1161

We will parse the data from a local json file and then return it as a list of reports.

class Report {
  final String date;
  final double vibration;

  Report({, this.vibration});

  factory Report.fromJson(Map<String, dynamic> json) {
    return Report(
        date: json['date'] as String, vibration: json['vibration'] as double);

For our example, we are only interested in the vibration data so that is all we will parse into the report object.

Next, we will convert our “API” data into model objects that the charts_flutter can use. charts_flutter expects a series of data associated with a domain and they provide a factory for populating the object they can use. An excerpt can be seen below.

 factory Series(
      {@required String id,
      @required List<T> data,
      @required TypedAccessorFn<T, D> domainFn,
      @required TypedAccessorFn<T, num> measureFn,

With this, we know we need to take our Report entries which include a date and a measurement for vibration and populate the Series factory that we can see above.

We will start by converting our report data yet again into something that we can use with the Series factory. Note: We could have done this when we got the data from the API, but sometimes it is better to keep the model for the rest of your domain pure and map it to the model that is currently being used when necessary. This helps create a separation and minimize impact should other areas of your application begin to rely on the report model. This is the model we will be mapping our entries to:

class VibrationData {
  final DateTime time;
  final double vibrationReading;

  VibrationData(this.time, this.vibrationReading);

Next, we will utilize the Series factory that we talked about earlier. For this, we have created a static function that takes in a list of VibrationData and returns a Series type that we can use with charts_flutter

  static List<Series> _createChartData(List<VibrationData> vibrationData) {

    var data = [
      new Series<VibrationData, DateTime>(
        id: 'Desktop',
        colorFn: (_, __) =>,
        domainFn: (VibrationData vibrationData, _) => vibrationData.time,
        measureFn: (VibrationData vibrationData, _) => vibrationData.vibrationReading,
        data: vibrationData,

    return data;


Once we’ve done that, we are ready to use the Series data with our chart! We’re almost there. All it has to have is our Series data

    List<common.Series<dynamic, DateTime>> seriesList, {etc})

So with that, we plop in the Seriesdata we created earlier and the chart will be rendered!


Our sample implementation, sets some of the optional fields on the TimeSeriesChart and wraps it in a FractionallySizedBox

                    child: TimeSeriesChart(
                      defaultRenderer: new LineRendererConfig(
                          includeArea: true, stacked: true),
                      animate: false,
                          new DateTimeAxisSpec(renderSpec: NoneRenderSpec()),

And we’re done! This will be rendered successfully on both mobile and web! 🙂

The full sample project is below. Thank you!


Google Maps for Flutter Web

This post will outline a Google Maps solution for Flutter Web.


The end result will have a google map centered on Texas with one marker and look like this:

To get started we need a google maps dependency. Open up pubspec.yaml and add the following:

google_maps: ">=3.0.0 <4.0.0"


Now we need to add our API Key for google maps to our index.html file in our web directory:

  <script src=""></script>


We are going to use dart inside an HtmlElementView which will return a widget that we can attach to our Flutter Web widget hierarchy. Without further ado, lets look at what that looks like in code.

Widget getMap() {
    String htmlId = "7";

    // ignore: undefined_prefixed_name
    ui.platformViewRegistry.registerViewFactory(htmlId, (int viewId) {

      final myLatlng = new LatLng(30.2669444, -97.7427778);

      final mapOptions = new MapOptions()
        ..zoom = 8 = new LatLng(30.2669444, -97.7427778);

      final elem = DivElement() = htmlId = "100%" = "100%" = 'none';

      final map = new GMap(elem, mapOptions);

        ..position = myLatlng = map
        ..title = 'Hello World!');

      return elem;

    return HtmlElementView(viewType: htmlId);

In the code above we use Dart to interact with the Google Map library we added to our project. The library wraps the the javascript API with dart and with that we get Dart IDE support and type safety. Looking at the block, we can see that we specify a LatLng object and set that to our map center. Lastly, we create a single marker and add it to the map.

The full source code for this example can be found here:

A youtube walkthrough video is here too:


How to programmatically set your app as the default app for a USB device on Android

If you are looking to set your app as the default app for a UsbDevice and do not need to do it programmatically, then I recommend you follow the answers outlined on this SO question. I have a project on GitHub that provides a sample implementation of one of the answers. If you need to set this permission programmatically, then keep reading…

The problem

Initially, we implemented the manual solution outlined in the SO question above on a kiosk type device, but it was still a challenge for our operations team to remember to check the appropriate box before leaving the customer site. This would leave our kiosk device in a state where it was unable to talk to a USB card reader attached to it. We can modify our staging process to do this manual operation, but we also have 100’s of tablets in the field already deployed that need remediation. Our platform runs on a modified tablet that we have root access on. I started to dig through the framework code to see how things work and to see if there was something we could do programmatically.

Some background

When we interact with UsbDevice‘s on Android, we typically do that through the UsbManager service. This is an Android system service that has an implementation exposed to App developers through the Android SDK and a platform implementation that is implemented in the Android Platform source. The SDK implementation (UsbManager) talks to the UsbService using AIDL. When we get the UsbManager service, the methods we call on it call the the IUsbManager interface methods that are implemented by the UsbService on the platform side.

We can see the UsbService getting started by the SystemServer platform code here

                if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)
                        || mPackageManager.hasSystemFeature(
                                PackageManager.FEATURE_USB_ACCESSORY)) {
                    // Manage USB host and device support
                    Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "StartUsbService");

That block will eventually end up instantiating the UsbService on the platform side here:

        public void onStart() {
            mUsbService = new UsbService(getContext());
            publishBinderService(Context.USB_SERVICE, mUsbService);

Don’t lose patience, we are almost to something interesting. The UsbService class handles the management of UsbDevices, but it delegates settings management to a UsbSettingsManager class. The UsbSettingsManager class references a settings file

        mSettingsFile = new AtomicFile(new File(

The settings file is read when the UsbSettingsManager is instantiated and the entries inside the file, called DeviceFilter entries, are added to an in memory map:

    // Maps DeviceFilter to user preferred application package
    private final HashMap<DeviceFilter, String> mDevicePreferenceMap =
            new HashMap<DeviceFilter, String>();

A sample usb_device_manager.xml file with on DeviceFilter

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
    <preference package="fs.instore">
        <usb-device vendor-id="2049" product-id="25" class="0" subclass="0" protocol="0" manufacturer-name="MagTek" product-name="USB Swipe Reader" serial-number="B48B67B" />

This is the file that is created when a user connects a UsbDevice, the framework detects that an app has an intent filter to look for this device, and the user selects “Use by default for this USB device” for the particular device. In short, this is the file we need to programmatically create.

Sample dialog:

The usb_device_manager.xml file is located at /data/system/users/0/usb_device_manager.xml on our device.

Lastly, we need to know that this file is only read at USBService start and the in memory map is not exposed publicly. This means that any modifications to this file will require a reboot or service stop/start before they can be used.

The solution

  1. Write the usb_device_manager.xml file with details for our app/device.
  2. Restart the UsbService so that it will read the modified file and use the new values.

Writing usb_device_manager.xml

We can use some of the publicly available platform code to help us write the usb_device_manager.xml file. The framework uses an XML Serializer to write to the settings file

We can borrow this to write the file or we can use the serializer built into the SDK.

We also need to know the model we need to write. Earlier I mentioned the entries in the XML file were DeviceFilter entries. The DeviceFilter class is a private static inner class of UsbSettingsManager. We can “borrow” this code and the DeviceFilter constructor that takes a UsbDevice and use it in our solution.

                    DeviceFilter deviceFilter = new DeviceFilter(usbDevice);

Now that we can recreate the same XML as the framework, we need to write to usb_device_manager.xml file. We can modify some of the platform code for our use.

    public static void writeSettingsLocked(Context context, DeviceFilter filter) {

        AtomicFile mSettingsFile = new AtomicFile(new File(context.getExternalFilesDir(null), "usb_device_manager.xml"));

        FileOutputStream fos = null;
        try {
            fos = mSettingsFile.startWrite();

            XmlSerializer serializer = new FastXmlSerializer();
            serializer.startDocument(null, true);
            serializer.setFeature("", true);
            serializer.startTag(null, "settings");

                serializer.startTag(null, "preference");
                serializer.attribute(null, "package", "fs.instore");
                serializer.endTag(null, "preference");

            serializer.endTag(null, "settings");

        } catch (IOException e) {
            Log.e(TAG, "Failed to write settings", e);
            if (fos != null) {

And once we are done writing the file we need to copy it to the correct location and make sure its file permissions are correct. The following commands are executing programmatically in a shell environment on the kiosk device.

    public static final String COMMAND_COPY_USB_FILE = "cp /sdcard/Android/data/com.whereisdarran.setusbdefault/files/usb_device_manager.xml /data/system/users/0/usb_device_manager.xml";
    public static final String COMMAND_CHOWN_USB_FILE = "chown system:system /data/system/users/0/usb_device_manager.xml";

Lastly, we need to restart the UsbService.

We can do that by simply issuing a reboot command. Alternatively, in a root shell we could issue the stop command followed by a start command to bounce all of the system services.

The full solution.

Thank you for reading!


How to setup boot on charge on TB-8504X/F running Nougat

Note: The Oreo ROM for this device supports fastboot oem off-mode-charge 0 This tutorial is for the Nougat ROM.

I was able to get it to work by updating the init.rc file

I found the on charger trigger and added the following lines below it:

setprop ro.bootmode "normal"
setprop sys.powerctl "reboot"

The entire trigger block ends up looking like this

on charger
    class_start charger
    setprop ro.bootmode "normal"
    setprop sys.powerctl "reboot"

You then need to repack and flash the boot image created after the updates.

Connect the device over USB

Power on device and get to bootloader mode

adb reboot bootloader 

To flash boot image execute the following command while in fastboot

fastboot flash boot new-boot.img

Note: This fix will cause the device to reboot when its plugged in even when shutting it off using the power button or software shutdown.


You can see the commit that contains these changes for my project here:


How to build TWRP for the TB-8504X/F

I recently started a project to build LineageOS for the Lenovo TB-8504X/F. Part of the journey typically involves building a custom recovery. TWRP is now the defacto so I started there.


  1. An Android build environment: or something like
  2. Repo Installed:


  1. Create a working directory and cd into it
    • mkdir twrp && cd twrp
  2. Check out limited manifest for building TWRP
    • repo init -u git:// -b twrp-7.1
  3. Sync
    • repo sync
  4. Make a directory for the tb_8504x device inside the repository directory and cd into it
    • mkdir -p device/lenovo/tb_8504x && cd device/lenovo/tb_8504x
  5. Clone the recovery source into the tb_85004x directory
    • git clone
  6. Go back to the top level directory and lets start to build
    • source build/
      lunch omni_tb_8504x-userdebug
      mka recoveryimage

If everything goes well you should have a recovery image available in out/target/product/tb_8504x/

Big thanks to

You can find the final output and a continuing thread here:


Tips and Tricks with Flutter HTTPClient

On a recent project, I had the opportunity to use the HTTPClient to integrate with a medical device. During the integration, I learned a few things that are not immediately apparent from the docs and wanted to share what I learned with you all.

Sometimes you need to use Charles Proxy to debug what is happening. The HTTPClient ignores proxies by default. We can modify the HTTPClient to use the proxy with the following configuration:

Using a Proxy (Charles Proxy/Fiddler)

HttpClient client = HttpClient();
client.findProxy = (uri) {
  return "PROXY;";

A more advanced configuration that will override a bad certificate check:

  static HttpClient getHttpClient() {
    HttpClient client = new HttpClient()
      ..findProxy = (uri) {
        return "PROXY;";
      ..badCertificateCallback =
      ((X509Certificate cert, String host, int port) => true);

    return client;

Configuring Digest Access Authentication

Basic Authentication is fairly straightforward to integrate, but Digest Access Authentication takes a little more work. Below you can see a sample Digest Access Authentication configuration:

static HttpClient getHttpClient(String userName, String password) {
HttpClient client = new HttpClient()
..badCertificateCallback =
((X509Certificate cert, String host, int port) => true)
client.authenticate = (uri, scheme, realm) {
uri, realm, new HttpClientDigestCredentials(userName, password));
return new Future.value(true);
return client;

The above snippet sets a function on the authenticate property of the client. When a resource the client is connecting to asks for authentication, we will supply HttpClientDigestCredentials to authenticate the request. I found this to work well with GET requests, but would fail with POST requests that included an attachment. This is because the client retries the request once credentials are provided, but it does not include attachments when replaying POST requests

Digest Authentication with POST

To handle the situation with Digest Authentication not working with POST, I created an authentication header that I would send with the initial POST request:

String ha1 = getHA1(userName, "websvc", password);
String ha2 = getHA2("POST", "/1.0/restrictedResource");
String response = getResponse(ha1, ha2, "3a15147c7e97b692f01a78ad53f887cd", "00000001", "SpL0Prz6");

HttpClientRequest httpClientRequest = await client.postUrl(Uri.parse(path));
httpClientRequest.headers.contentLength = csvAttachment.length;
httpClientRequest.headers.contentType = ContentType.text;
httpClientRequest.headers.add("Authorization", 'Digest username="$userName", realm="websvc", nonce="3a15147c7e97b692f01a78ad53f887cd", uri="/1.0/restrictedResource", algorithm="MD5", qop=auth, nc=00000001, cnonce="SpL0Prz6", response="$response"');


return await httpClientRequest.close();

// Helper methods

static String getHA1(String username, String realm, String password) {
List<int> toConvert = [];
return md5.convert(toConvert).toString();

static String getHA2(String method, String url) {
List<int> toConvert = [];
return md5.convert(toConvert).toString();

static String getResponse(String ha1, String ha2, String nonce, String nonceCount, String cnonce) {
List<int> toConvert = [];
return md5.convert(toConvert).toString();

Those are a few tips/tricks I picked up. Maybe they will help you along your journey. Thank you!