1

Inside the BluetoothGattCallback method onConnectionStateChange I am checking for the successful connection with the BLE device and calling the discoverServices method afterwards. The BLE device needs a pin entry (prompt by Android) for an successful pairing. I want to discover all available services immediately after connecting to the device because when switching to the main activity of the application there should be data from the available characteristics already displayed.

I tried to analyze the functionality of the onConnectionStateChange method and the behavior of the BLE device with pin. Unfortunately the method is called once you initialize the connection and again after successfully entering the pin. There isn't a difference within the receiving state codes. Status and newState are exactly the same when initializing and after successful pin entry and connection. Therefore i added this workaround with the Thread.sleep method to wait for the user entry of the pin and call afterwards the discoverServices method. But this workaround is not very practical because the user need to enter the pin within these 10 seconds. If not the connection is successful but the services won't be discovered.

How can i check or differ between these two states? Initializing the connection and successful enter of the pin?

@Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        Log.d("KOPPLUNG", "In onConnectionStateChange with status: " + status + " and newState: " + newState);
        if (newState == BluetoothProfile.STATE_CONNECTED) {
            broadcastUpdate(ACTION_GATT_CONNECTED, mCallbackBleAddress);
            Log.i(TAG, "Connected to GATT server." + mCallbackBleAddress);
            // Attempts to discover services after successful connection.

            try {
                Thread.sleep(10000);
            } catch (Exception e) {

            }

            for(int i = 0; i<5; i++){
                if(mBoltDeviceHandler.getBoltDevice(mCallbackBleAddress).getBluetoothGatt().discoverServices()){
                    Log.i(TAG, "Attempting to start service discovery:" + true);
                    break;
                }
            }



        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                 switch (status){
                 case 0:  Log.i(TAG, "Sucessfully Disconnected from GATT server.");
                          break;
                 case 133:  // Handle internal Android Bug
                         Log.i(TAG, "Connection aborted, Android Error 133");
                         broadcastUpdate(ACTION_GATT_CONNECTION_NOT_SUCCESSFUL, mCallbackBleAddress);
                         break;
                  default:  Log.i(TAG, "Unexpected Disconnection from GATT server. Errorcode: "+status);
                         broadcastUpdate(ACTION_GATT_DISCONNECTED, mCallbackBleAddress);
                         autoconnect(gatt.getDevice());

            }

Addition

The code for building up the connection is divided up in three different classes. The application starts with an scan activity where available devices are being searched and listed. By clicking on one list item (device) the connection process will be started.

onListItemClick

@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
    final BluetoothDevice device = mLeDeviceListAdapter.getDevice(position);
    if (device == null) return;
    Context mContext = getApplicationContext();
    BoltDevice boltDevice = new BoltDevice(device,mContext);

    int i = mBoltDeviceHandler.addBoltDevice(boltDevice);
    Intent resultIntent = new Intent();
    resultIntent.putExtra("IN_CONNECTION", boltDevice.getBleAddress());
    resultIntent.putExtra("POSITION",i);
    if(i != -1){
        setResult(Activity.RESULT_OK, resultIntent);
    }
    else {
        setResult(Activity.RESULT_CANCELED, resultIntent);
    }
    finish();
}

By running the above code an object from type BoltDevice is being created and added to the BoltDeviceHandler.

Relevant code from BoltDevice class

public BoltDevice(BluetoothDevice device, Context context) {
    mContext = context;
    this.mBluetoothDevice = device;
    this.mBleAddress = device.getAddress();
    this.mDeviceName = device.getName();
    mBatteryLevel = 0;
    mSpecialBolt = false;
    //Workarround for 133 error taken from: https://github.com/googlesamples/android-BluetoothLeGatt/issues/44
    mStartGattHandler.postDelayed(mStartGattRunnable, START_GATT_DELAY);

}

public void closeGatt() {
    if (mBluetoothGatt != null) {
        mBluetoothGatt.disconnect();
        mBluetoothGatt.close();
        mBluetoothGatt = null;
    }
}

public boolean connect() {
    // Previously connected device.  Try to reconnect.
    if (mBluetoothGatt != null) {
        Log.d(TAG, "Trying to connect with existing bluetoothGATT to: " + mBleAddress);
        if (mBluetoothGatt.connect()) {
            return true;
        } else {
            Log.d(TAG, "Could not connect to existing bluetoothGATT: " + mBleAddress);
            return false;
        }
    }

    // We want to directly connect to the device, so we are setting the autoConnect
    // parameter to false.
    mBluetoothLeService.connect(mBluetoothDevice);
    Log.d(TAG, "Trying to create a new connection to: " + mBluetoothDevice.getAddress());
    return true;
}

public boolean disconnect() {
    if (mBluetoothGatt != null) {
        mBluetoothGatt.disconnect();
        return true;
    } else {
        return false;
    }
}

public void bindService(){
    Intent gattServiceIntent = new Intent(mContext, BluetoothLeService.class);
}       

Relevant code from the MainActivity (active after the ScanActivity is finished)

 @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    switch(requestCode) {
        case (5) : {
            if (resultCode == Activity.RESULT_OK) {
                int i = data.getIntExtra("POSITION",-1);
                mReconnect[i] = false;
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        if(!isFinishing()) {
                            updateText(i,true);
                            ToastCompat.makeText(MainActivity.this, "In Connection!", ToastCompat.LENGTH_SHORT).show();
                        }
                    }
                });
            }
            if (resultCode == Activity.RESULT_CANCELED) {
                if(data!=null) {
                    int i = mBoltDeviceHandler.getBoltDevicePositionInList(data.getStringExtra("IN_CONNECTION"));
                    mReconnect[i] = true;
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            if(!isFinishing()) {
                                ToastCompat.makeText(MainActivity.this, "In Connection!", ToastCompat.LENGTH_SHORT).show();
                            }
                        }
                    });
                }
                else{
                    ToastCompat.makeText(this,"No device selected!", ToastCompat.LENGTH_SHORT).show();
                }
            }
            break;
        }
    }
}

(...)

private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        final String address = intent.getStringExtra(BluetoothLeService.EXTRA_ADDRESS);
        if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
            ToastCompat.makeText(MainActivity.this, mBoltDeviceHandler.getBoltDevice(address).getDeviceName() + " " + getResources().getString(R.string.BLE_Connected), ToastCompat.LENGTH_SHORT).show();
            updateText(mBoltDeviceHandler.getBoltDevicePositionInList(address), false);
            ToastCompat.makeText(MainActivity.this, mBoltDeviceHandler.getBoltDevice(address).getDeviceName()+ ": "+ getResources().getString(R.string.BLE_ServicesDiscovered), ToastCompat.LENGTH_SHORT).show();
        } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
(...)
DanLand
  • 45
  • 6
  • can you show code for connection or pairing – Jayanth Dec 04 '20 at 10:12
  • I have edited my question and added additional information – DanLand Dec 04 '20 at 10:28
  • BLE usually allows scanning of all characteristics after connecting without pairing needed. Pairing should only happen when a secured characteristic is accessed. I understand from your description that android asks for a pin after connecting to the device without accessing a characteristic. Is that correct? – Michael Kotzjan Dec 04 '20 at 12:29
  • Yes exactly. After selecting the list item and initializing the connection process the prompt to enter the pin is shown. – DanLand Dec 04 '20 at 12:54

1 Answers1

0

First of all, I would recommend using the Nordic library. That saved me a lot of headhacke when working with BLE on android and allow to keep a clean architecture.

You should have something of the state machine like :

  • connect to device
  • request GATT services / characteristic
  • request for the user code on Android
  • send user code
  • retrieve data over BLE

Also, after receiving a onConnection status change notifcation, you should xwait a few ms before starting discovering GATT

The onConnectionStateChange event is triggered just after the Android connects to a device.Moreover, when the device has Service Changed indication enabled, and the list of services has changed (e.g. using the DFU), the indication is received few hundred milliseconds later, depending on the connection interval. When received, Android will start performing a service discovery operation on its own, internally, and will NOT notify the app that services has changed.

public final void onConnectionStateChange(@NonNull final BluetoothGatt gatt,
                                                final int status, final int newState) {

    if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
        
        // Sometimes, when a notification/indication is received after the device got
        // disconnected, the Android calls onConnectionStateChanged again, with state
        // STATE_CONNECTED.
        // See: https://github.com/NordicSemiconductor/Android-BLE-Library/issues/43
        if (bluetoothDevice == null) {
            Log.e(TAG, "Device received notification after disconnection.");
            log(Log.DEBUG, "gatt.close()");
            try {
                gatt.close();
            } catch (final Throwable t) {
                // ignore
            }
            return;
        }

        // Notify the parent activity/service
        Log("Connected to " + gatt.Device.Address);
        isConnected = true;
        connectionState = State.Connected;
        OnDeviceConnected(gatt.Device);

        /*
        * TODO: Please calculate the proper delay that will work in your solution.
        * If your device does not use Service Change indication (for example does not have DFU) the delay may be 0.
        */
        var delay = 1600; // around 1600 ms is required when connection interval is ~45ms.

        postDelayed(() -> gatt.DiscoverServices(), delay);


    } else {
        if (newState == ProfileState.Disconnected)
        {               
            var wasConnected = IsConnected;
            if (gatt != null)
            {
                NotifyDeviceDisconnected(gatt.Device); // This sets the mConnected flag to false
                if (_initialConnection) ConnectDevice(gatt.Device);

                if (wasConnected || status == GattStatus.Success)
                    return;
            }
        }
        /*
        * Connection attempt did fail! Retry possible ?
        */ 
        onError(gatt.Device, Error.LinkLost, status);
    }
}
Florian Burel
  • 3,408
  • 1
  • 19
  • 20
  • So it is the wrong way to try to connect to the device (and receive the pin prompt from android) and discover the services after that? My problem is that the onConnectionChange method is called by pressing the list item and initializing the connection progress, than the dialog is shown to enter the pin and after successfully entering the correct pin the method is called again with the same status codes. If i could differ between these two stages i could call discoverServices after the second call. – DanLand Dec 04 '20 at 11:28
  • If you meant to send the pin to the device, it would be better to connect and discover, then the pin on the proper caracteristic. Discovering the services right after connection allow you to ensure you have a proper connection establish and your device is rin a good state (correct services in places) and ready to receive data. alternatively, you can ak for the pin before establishing the connection so Pin > connect > discover or connect > discover > pin, but I see no interest in sepparating connect and discovering since a connection would be useless without the services. – Florian Burel Dec 04 '20 at 13:02
  • if you really want to stick with connect > pIN > discover, you can of course, just keepp a value 'connected_state' that you would set on true the first time you receive the onconnectionStatuschanged, and ignore the call that you get after that – Florian Burel Dec 04 '20 at 13:04