Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

Here my BluetoothGattServerCallback where I handle two events STATE_CONNECTED and STATE_DISCONNECTED

    private static class CallBack extends BluetoothGattServerCallback {
        private Set<BluetoothDevice> mRegisteredDevices = new HashSet<>();
        private MainActivity mMainActivity;
        public CallBack(Set<BluetoothDevice> registeredDevices, MainActivity mainActivity){
            mRegisteredDevices = registeredDevices;
            mMainActivity =  mainActivity;
        @Override
        public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
            Log.i(TAG,"onConnectionStateChange()");
            switch (newState)
                case STATE_CONNECTED :
                    handleStateConnected( device,  status,  newState);
                    break;
                case STATE_DISCONNECTED :
                    handleStateDisconnected( device,  status, newState);
                    break;

Here is handler STATE_CONNECTED

    private void handleStateConnected(BluetoothDevice device, int status, int newState)
        Log.i(TAG, "BluetoothDevice CONNECTED: " + device);
        mRegisteredDevices.add(device);
        stopAdvertising();

Here is handler STATE_DISCONNECTED

    private void handleStateDisconnected(BluetoothDevice device, int status, int newState)
        Log.i(TAG, "BluetoothDevice DISCONNECTED: " + device);
        mRegisteredDevices.remove(device);
        startAdvertising();

On STATE_CONNECTED I would like my phone to stop advertising, and on STATE_DISCONNECTED I would like the phone to resume advertising; the default behaviour appears to be that the device will continue advertising after it's connected, so I added stopAdvertising() and startAdvertising() in the event handlers handleStateConnected() and handleStateDisconnected() respectively.

The phone now goes into an ifinite loop of connecting and recconnecting, the reason being that after calling stopAdvertising() the phone will disconnect, causing it to begin advertising again, after which it will connect.

The control flow of my app is to begin advertising, then set up GATT server:

private void initServer(){
    Log.v(TAG,"initServer()");
    mBluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
    BluetoothAdapter bluetoothAdapter = mBluetoothManager.getAdapter();
    // We can't continue without proper Bluetooth support
    if (!checkBluetoothSupport(bluetoothAdapter)) {
        finish();
    // Register for system Bluetooth events (GATT server started in receiver)
    IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
    registerReceiver(mBluetoothReceiver, filter);
    if (!bluetoothAdapter.isEnabled()) {
        Log.d(TAG, "Bluetooth is currently disabled...enabling");
        bluetoothAdapter.enable();
    } else {
        startAdvertising();
        startServer();

Here is startAdvertising()

public static void startAdvertising() {
    Log.i(TAG,"startAdvertising()");
    BluetoothAdapter bluetoothAdapter = mBluetoothManager.getAdapter();
    mBluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser();
    if (mBluetoothLeAdvertiser == null) {
        Log.w(TAG, "Failed to create mBleAdvertiser");
        return;
    Log.v(TAG,"created advertizer");
    AdvertiseSettings settings = new AdvertiseSettings.Builder()
            .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED)
            .setConnectable(true)
            .setTimeout(0) // Limit advertising to a given amount of time A value of 0 will disable the time limit
            .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
            .build();
    boolean isNameChanged = BluetoothAdapter.getDefaultAdapter().setName(DEVICE_NAME);
    if(isNameChanged) Log.d(TAG,"Device name changed successfully.");
    AdvertiseData data = new AdvertiseData.Builder()
            .setIncludeDeviceName(true)
            .setIncludeTxPowerLevel(false)
            .addServiceUuid(ParcelUuid.fromString(Services.GloService.UUID))
            .build();
    mBluetoothLeAdvertiser
            .startAdvertising(settings, data, mAdvertiseCallback);

Here is startServer()

private void startServer() {
    Log.i(TAG,"startServer()");
    mBluetoothGattServer =
            MyProfile.getOpenServer(this, mBluetoothManager,mRegisteredDevices,this);
    if (mBluetoothGattServer == null) {
        Log.w(TAG, "Unable to create GATT server");
        return;

And here is MyProfile.getOpenServer which returns the server object initialized with the call back declared at the top of the post.

public static BluetoothGattServer getOpenServer(Context ctx,
                                                BluetoothManager bluManager,
                                                Set<BluetoothDevice> registeredDevices,
                                                MainActivity mainActivity){
    mBluetoothManager = bluManager;
    CallBack callBack = new CallBack(registeredDevices, mainActivity);
    BluetoothGattServer server = bluManager.openGattServer(ctx, callBack);
    BluetoothGattService myService = getMyService();
    server.addService(myService);
    return server;
                This has been a problem I've been working around with Android for years. There's no good way to fix this that works across all devices.  I've had the luxury of working on a AOSP platform lately and we patched the problem down at the BlueDroid level.  That's where the root of the problem is.
– Greg Moens
                Dec 1, 2018 at 21:14
                Nothing I found that works on all devices. There is one fix that seemed to help. I'll have to dig it up for you. Give me a bit.
– Greg Moens
                Dec 1, 2018 at 23:04

Here's one thing I have done in the past that helps with this problem. No guarantees, but it's the best workaround I've found.

When you get a STATE_CONNECTED event on the server side, call BluetoothGattServer.connect(BluetoothDevice, boolean). Here is a snippet that I've used before.

        @Override
        public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
            switch (newState) {
                case BluetoothProfile.STATE_CONNECTED:
                    gattServer.connect(device, false); // prevents disconnection when advertising stops
                    // stop advertising here or whatever else you need to do
                    break;
                Unfortunately it didn't work for me. I am also confused why one should call connect on a device which is already connected. I wonder if maybe BluetoothGattServer.connect() method is for being used in conjunction with some kind of advertisement listener, as in the packet is received, parsed, then on some data in the payload, the device connected to.
– the_prole
                Dec 2, 2018 at 0:02
                Sorry it didn't work.  It's equally confusing for me, I always assumed that it put the server into some state that realized it shouldn't disconnect. This bug has existed since lollipop and doesn't appear to be going anywhere.
– Greg Moens
                Dec 2, 2018 at 2:14
        

Thanks for contributing an answer to Stack Overflow!

  • Please be sure to answer the question. Provide details and share your research!

But avoid

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.