Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android BluetoothSocket write fails on 4.2.2

I'm testing on both an ASUS MemoPad HD (4.2.2) and an LG P769 (4.1.2). I wrote a simple program to illustrate my issue:

package wat.bluetoothtester;

import java.util.*;
import java.io.*;

import android.app.Activity;
import android.os.Bundle;
import android.bluetooth.*;
import android.view.*;
import android.util.*;

public class BluetoothTester extends Activity
{
    private final String ADDR = "00:02:72:C6:C2:3C";
    private final UUID _UUID = UUID.fromString("ca444490-3569-4c82-b505-defbc71e9868");

    private BluetoothSocket _socket;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    public void send(View v){
        try {
            this._socket.getOutputStream().write('x');
        }
        catch (IOException e){
            Log.e("", e.toString());
        }
    }

    public void connect(View v){
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        BluetoothDevice device = adapter.getRemoteDevice(ADDR);

        try {
            this._socket = device.createInsecureRfcommSocketToServiceRecord(_UUID);
            this._socket.connect();
        }
        catch (IOException e){
            Log.e("", e.toString());
        }
    }

    public void disconnect(View v){
        try {
            this._socket.close();
        }
        catch (IOException e){
            Log.e("", e.toString());
        }
    }
}

It blocks, I know, but that's not the issue. Got a simple layout with 3 buttons, as you might be able to guess from the code. I'll post if necessary. I also have a simple Python server running on my computer which just spits out whatever data it receives, and never sends any data.

Anyway, the first time I run this on my 4.2.2 device, it works great. I can connect and disconnect at will, send data, no problem. Now, since I'm developing an app, I have to frequently push a new APK to the device. As soon as I do this, whether or not I disconnect, the writes start failing. The strangest part is that the server actually receives the data and prints it, but the write() calls still block:

11-19 12:34:30.127: E/(3484): java.io.IOException: [JSR82] write: write() failed.
11-19 12:34:30.127: E/(3484):   at android.bluetooth.BluetoothSocket.write(BluetoothSocket.java:702)
11-19 12:34:30.127: E/(3484):   at android.bluetooth.BluetoothOutputStream.write(BluetoothOutputStream.java:56)
11-19 12:34:30.127: E/(3484):   at wat.bluetoothtester.BluetoothTester.send(BluetoothTester.java:29)
11-19 12:34:30.127: E/(3484):   at java.lang.reflect.Method.invokeNative(Native Method)
11-19 12:34:30.127: E/(3484):   at java.lang.reflect.Method.invoke(Method.java:511)
11-19 12:34:30.127: E/(3484):   at android.view.View$1.onClick(View.java:3633)
11-19 12:34:30.127: E/(3484):   at android.view.View.performClick(View.java:4243)
11-19 12:34:30.127: E/(3484):   at android.view.View$PerformClick.run(View.java:17520)
11-19 12:34:30.127: E/(3484):   at android.os.Handler.handleCallback(Handler.java:725)
11-19 12:34:30.127: E/(3484):   at android.os.Handler.dispatchMessage(Handler.java:92)
11-19 12:34:30.127: E/(3484):   at android.os.Looper.loop(Looper.java:153)
11-19 12:34:30.127: E/(3484):   at android.app.ActivityThread.main(ActivityThread.java:5299)
11-19 12:34:30.127: E/(3484):   at java.lang.reflect.Method.invokeNative(Native Method)
11-19 12:34:30.127: E/(3484):   at java.lang.reflect.Method.invoke(Method.java:511)
11-19 12:34:30.127: E/(3484):   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:833)
11-19 12:34:30.127: E/(3484):   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600)
11-19 12:34:30.127: E/(3484):   at dalvik.system.NativeStart.main(Native Method)

So clearly this throws an IOException, even though the data has successfully been sent. This means that the program technically works, but write() blocks the UI for around 10-15 seconds until the exception is thrown. I've tried putting the write() into its own thread, then just creating a TX buffer which it empties at will. This of course doesn't cause the UI to block, but I need to wait for the write() timeout, so that doesn't really help me either. This is the case until I reboot the device. Connect/disconnect both still work fine.

This doesn't happen on my 4.1.2 device, no matter how many times I upload a new APK or kill the process.

I've also confirmed the same problem when using BlueTerm, a terminal-type app for RFCOMM serial connections (actually quite good, plus the source is available).

I've got both devices rooted, and I don't really want to go down this road, but is there a way to restart the Bluetooth service or manually purge the connection pool, or something?

like image 870
Andrew Avatar asked Nov 02 '22 10:11

Andrew


1 Answers

The MediaTek JNI implementation of the JSR82 for Android doesn't work very well

  • android_server_BluetoothSocketService.cpp (writeNative(): Long time no see a goto)
  • BluetoothSocketService.java
  • BluetoothSocket.java

It creates an array to store the connections context:

btmtk_jsr82_mmi_context_struct  g_jsr82MMIContext[JSR82_PORT_NUM];

When a new socket is created it uses the fist context position not inUse and unless you close the socket properly (be aware that this is a shared service) the inUse flag is not cleared.

The problem is that only g_jsr82MMIContext[0] seems to receive the EVENT_JSR82_MMI_TX_READY_IND events properly at the end of a write()

The sockets file descriptors seems to be hardcoded and for the socket at g_jsr82MMIContext[0] is 0x8000, so with reflection you can "fake" that socket and close it to clean the g_jsr82MMIContext[0].inUse flag, so next time it will be availabe.

    public void connect(View v){

        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        BluetoothDevice device = adapter.getRemoteDevice(ADDR);

        try {
            BluetoothSocket bs = device.createRfcommSocketToServiceRecord(_UUID);
            Field f = bs.getClass().getDeclaredField("mFdHandle");
            f.setAccessible(true);
            f.set(bs, 0x8000);
            bs.close();
            Thread.sleep(2000); // Just in case the socket was really connected 
        } catch (Exception e) {
            Log.e(TAG, "Reset Failed", e);
        } 

        try {
            this._socket = device.createRfcommSocketToServiceRecord(_UUID);
            this._socket.connect();
        } catch (IOException e) {
            Log.e(TAG, "create() failed", e);
        }
    }
like image 148
jla Avatar answered Nov 11 '22 10:11

jla