JAVA

Jose Luis Arenas
Devs @ FOODit
Published in
3 min readApr 14, 2015

--

Reflection Workarounds

for Android Bluetooth

In FOODit we design technology to help restaurants connect with their customers and showcase their food online in the best way possible.

Most of our restaurants use tablets with bluetooth printers to manage their online orders. As a customer places an order in our system, a receipt is printed at the kitchen’s restaurant.

Some of our customers have been reporting random problems regarding the printer reliability and all of them had two things in common:

  • They use the same Lenovo model tablet with Android 4.2.2
  • Once the printer stopped working, rebooting the tablet was the only way to get it working again.

The easiest way for us to replicate the problem was to switch off the printer while it was paired with the tablet. Once the printer was switched on again it paired correctly, but when we tried to print after reconnecting the answer we were getting was:

java.io.IOException: [JSR82] write: write() failed.    at android.bluetooth.BluetoothSocket.write
(BluetoothSocket.java:702)

Searching for that error we only found a couple of unanswered questions on StackOverflow and luckily, a BluetoothSocket.java file with the infamous line throw new IOException(“[JSR82] write: write() failed.”); on a GitHub repo. It seems like the MediaTek JNI implementation of the JSR82 for Android:

The BluetoothSocket write() uses the BluetoothSocketService that finally calls to the JNI writeNative() implemented in BluetoothSocketService.cpp

Looking at the JNI implementation, it seems that writeNative() waits for a WRITE_DATA_TIMEOUT time for an EVENT_JSR82_MMI_TX_READY_IND event to properly signal the end of the write, then the total written bytes are returned. If the event is not received after the timeout, a -1 is returned, which results on our beloved exception.

We only got that event when we connected to the printer the first time, so we needed to figure out how the connections were managed.

When the JNI interface is initialized, it creates an array to store the connections context:

btmtk_jsr82_mmi_context_struct g_jsr82MMIContext[JSR82_PORT_NUM];
jboolean btmtk_jsr82_allocate_cntx(int *iIndex)
{
int iCount = 0;
for (iCount = 0; iCount < JSR82_PORT_NUM; iCount++)
{
if (JNI_FALSE == g_jsr82MMIContext[iCount].inUse)
{
...
...
*iIndex = iCount;
return JNI_TRUE;
}
}
return JNI_FALSE;
}

When a new socket is created it uses the first context position not inUse and unless you close the socket properly — remember, this is a shared service- the inUse flag is not cleared.

The problem is that only the first context, g_jsr82MMIContext[0], seems to receive the EVENT_JSR82_MMI_TX_READY_IND events properly at the end of a write(), all the other contexts throw an exception on write.

As this drivers are part of the Android Services and we don’t expect a system update from this particular model, lets see what we can do to force the use of the only good one.

So we only need to set inUse to false and to achieve that, the easiest way is to close the socket thats is going to call the following methods:

  • BluetoothSocket: close()
  • BluetoothSocket: mService.destroy(mFdHandle)
  • BluetoothSocketService: destroyNative(fdHandle)
  • JNI: btmtk_jsr82_search_cntx_by_fd(int fdHandle, int *iIndex)
  • JNI: btmtk_jsr82_clear_cntx(iCntxIndex);
  • JNI: g_jsr82MMIContext[iIndex].inUse = JNI_FALSE;

Mapping between the context position and the file descriptor is done by:

#define ANDROID_JSR82_FD_BASE 0x8000
int btmtk_jsr82_fd_to_context(int fd) {
return (fd — ANDROID_JSR82_FD_BASE);
}

The sockets file descriptors seems to be hardcoded, so we only need to instantiate a socket change its file descriptor to 0x8000 and close it in order to clean the inUse flag. So the first context position gets free for the next connection, easy peasey. Well not yet, as we have this:

public final class BluetoothSocket implements Closeable, Parcelable {
...
private int mFdHandle; /* The handle of this port. */
...
}

Lets put a little bit of reflection to work:

So we finally are able to get the “good” one. Of course this workaround make impossible to use other bluetooth devices apart of the printer, but thats ok for a dedicated device.

Jose Luis Arenas is a Senior Developer at FOODit — a young startup with a passion for food and tech, creating software to help independent restaurants grow. FOODit is always on the lookout for talented developers and is currently hiring. Connect with us via LinkedIn and Twitter.

Image by Francisco Osorio: Lloyd’s building reflection (Cc-by-2.0)

--

--