YUSMLE
YUSMLE

Reputation: 725

Unstable BLE Connection in Android 6 (Marshmallow)

I'm developing an android app that works with Bluetooth LE. It works well on Android 4.3 or 5.0.1 and on various devices, but in Android M (6.0.1) it isn't stable. I wrote a lite sample project that has same behavior and issues, and below is my all code;

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class MainActivity extends ActionBarActivity {
    private BluetoothAdapter mBluetoothAdapter;
    private int REQUEST_ENABLE_BT = 1;
    private Handler mHandler;
    private static final long SCAN_PERIOD = 2000;
    private BluetoothLeScanner mLEScanner;
    private ScanSettings settings;
    private List<ScanFilter> filters;
    private BluetoothGatt mGatt;

    private boolean connectionState = false, isScanning = false;
    private TextView rssiTxt;
    private RSSITimer rssiTimer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler = new Handler();
        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            Toast.makeText(this, "BLE Not Supported",
                    Toast.LENGTH_SHORT).show();
            finish();
        }
        final BluetoothManager bluetoothManager =
                (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothAdapter = bluetoothManager.getAdapter();

        // VIEW
        rssiTxt = (TextView) findViewById(R.id.rssiTxt);

        (rssiTimer = new RSSITimer()).startTimer();
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
        } else {
            if (Build.VERSION.SDK_INT >= 21) {
                mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
                settings = new ScanSettings.Builder()
                        .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
                        .build();
                filters = new ArrayList<ScanFilter>();
            }
            scanLeDevice(true);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) {
            //scanLeDevice(false);
        }
    }

    @Override
    protected void onDestroy() {
        rssiTimer.stopTimerTask();

        if (mGatt == null) {
            return;
        }
        mGatt.close();
        mGatt = null;
        super.onDestroy();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_ENABLE_BT) {
            if (resultCode == Activity.RESULT_CANCELED) {
                //Bluetooth not enabled.
                finish();
                return;
            }
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

    private void scanLeDevice(final boolean enable) {
        if(isScanning) {
            return;
        } else {
            isScanning = true;
        }

        if (enable) {
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (Build.VERSION.SDK_INT < 21) {
                        mBluetoothAdapter.stopLeScan(mLeScanCallback);

                        isScanning = false;
                    } else {
                        mLEScanner.stopScan(mScanCallback);

                        isScanning = false;
                    }
                }
            }, SCAN_PERIOD);
            if (Build.VERSION.SDK_INT < 21) {
                mBluetoothAdapter.startLeScan(mLeScanCallback);
            } else {
                mLEScanner.startScan(filters, settings, mScanCallback);
            }
        } else {
            if (Build.VERSION.SDK_INT < 21) {
                mBluetoothAdapter.stopLeScan(mLeScanCallback);
            } else {
                mLEScanner.stopScan(mScanCallback);
            }
        }
    }

    private ScanCallback mScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            Log.i("callbackType", String.valueOf(callbackType));
            Log.i("result", result.toString());
            BluetoothDevice btDevice = result.getDevice();

            if ("PRODi".equals(device.getName()) || "PRODi(TEST)".equals(device.getName())) {
                connectToDevice(btDevice);
            }
        }

        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            for (ScanResult sr : results) {
                Log.i("ScanResult - Results", sr.toString());
            }
        }

        @Override
        public void onScanFailed(int errorCode) {
            Log.e("Scan Failed", "Error Code: " + errorCode);
        }
    };

    private BluetoothAdapter.LeScanCallback mLeScanCallback =
            new BluetoothAdapter.LeScanCallback() {
                @Override
                public void onLeScan(final BluetoothDevice device, int rssi,
                                     byte[] scanRecord) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Log.i("onLeScan", device.toString());

                            if ("PRODi".equals(device.getName()) || "PRODi(TEST)".equals(device.getName())) {
                                connectToDevice(device);
                            }
                        }
                    });
                }
            };

    public void connectToDevice(BluetoothDevice device) {
        if (mGatt == null) {
            mGatt = device.connectGatt(getApplicationContext(), false, gattCallback);
            scanLeDevice(false);// will stop after first device detection

            return;
        } else {
            mGatt.connect();
            scanLeDevice(false);// will stop after first device detection
        }
    }

    private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            switch (newState) {
                case BluetoothProfile.STATE_CONNECTED:

                    Log.i("gattCallback", "STATE_CONNECTED");
                    gatt.discoverServices();
                    connectionState = true;
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(MainActivity.this, "STATE_CONNECTED", Toast.LENGTH_SHORT).show();
                        }
                    });
                    break;
                case BluetoothProfile.STATE_DISCONNECTED:

                    Log.e("gattCallback", "STATE_DISCONNECTED");
                    connectionState = false;
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(MainActivity.this, "STATE_DISCONNECTED", Toast.LENGTH_SHORT).show();
                        }
                    });
                    break;
                default:
                    Log.e("gattCallback", "STATE_OTHER");
            }

        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            List<BluetoothGattService> services = gatt.getServices();
            Log.i("onServicesDiscovered", services.toString());
            gatt.readCharacteristic(services.get(1).getCharacteristics().get(0));
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt,
                                         BluetoothGattCharacteristic
                                                 characteristic, int status) {
            Log.i("onCharacteristicRead", characteristic.toString());
            gatt.disconnect();
        }

        @Override
        public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
            super.onReadRemoteRssi(gatt, rssi, status);

            final int rssi2 = rssi;
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    rssiTxt.setText("RSSI: " + rssi2);
                }
            });
        }
    };

    /*TIMER for Read RSSI*/////////////////////////////////////////////////////////////////////////////////////////
    class RSSITimer {
        //we are going to use a handler to be able to run in our TimerTask
        final Handler handler = new Handler();
        private Timer     timer;
        private TimerTask timerTask;

        private int interval = 1000;

        public void startTimer() {
            //set a new Timer
            timer = new Timer();
            //initialize the TimerTask's job
            initializeTimerTask();
            //schedule the timer, after the first 1000ms the TimerTask will run every [INTERVAL]ms
            timer.schedule(timerTask, 1000, interval); //
        }

        public void stopTimerTask() {
            //stop the timer, if it's not already null
            if (timer != null)
            {
                timer.cancel();
                timer = null;
            }
        }

        public void initializeTimerTask() {
            timerTask = new TimerTask() {
                public void run() {
                    handler.post(new Runnable() {
                        public void run() {

                            if (mBluetoothAdapter.isEnabled()) {
                                if (connectionState == false) {
                                    scanLeDevice(true);
                                } else {
                                    mGatt.readRemoteRssi();
                                }
                            }
                        }
                    });
                }
            };
        }
    }
}

It have this various issues;

I know that DeadObjectException occured in BluetthGatt during this issues and readRemoteRssi() returned false, but can't solve it.

I know that Android 6 have some changes in BLE and I searched a lot and Studied Documents, but no result! But I found that when I set the interval to 100 instead of 1000, it works BETTER, but not Absolutely (on Android 6.0.1). Yes, I am sure that interval and how to implement it (Runnable, AlarmManager, TimerTask, ScheduledExecutorService, ...) are effective to that, but WHY?! It's my qustion.

Have anybody idea? Thanks and Sorry about my English.

UPDATE: Here is a link to my log, when I put appliction under some pressure. Sorry, it is bigger than stackoverflow capacity!! I see clearly that Bluetooth restarted in it...

Upvotes: 2

Views: 1836

Answers (3)

HamidReza
HamidReza

Reputation: 727

Enable "Bluetooth HCI snoop log" and use Wireshark to analyze packets. This way you can see Bluetooth stack events. Bluetooth HCI snoop log Menu

Upvotes: 1

p2pkit
p2pkit

Reputation: 1215

It looks like you are scanning and connecting/reading/writing at the same time. On Android this leads to stability problems and the bluetooth stack can crash. Try to go easier on the bluetooth stack, never scan and connect at the same time, only connect to one device and wait until the callbacks returned before doing the next thing, you should even add some breaks between your ble tasks. We had a lot of issues when doing too much on the ble stack at the same time, like a crashing bluetooth stack, not able to connect to devices anymore until you restart the device and so on.

Upvotes: 0

Emil
Emil

Reputation: 18472

don't connect again, after disconnect

Execute device.connectGatt(getApplicationContext(), true, gattCallback); instead of device.connectGatt(getApplicationContext(), false, gattCallback); to automatically reconnect.

don't work when application goes to background

You need to have a foreground service or something else in your application process that prevents your process from being killed. This is because the BLE apis are based on callbacks and not intents.

bluetooth chip reset between disconnect and trying to connect again!! don't receive callback of readRemoteRssi() after few seconds

Definitely a bug in Android. File a bug report. Could you post the FULL logcat output (not only the output filtered from your application)?

don't receive callback of readRemoteRssi() after few seconds

No idea about this one...

Upvotes: 0

Related Questions