0

We are using a ble device that has a noticeable characteristic, and we use the EnableNotification to get data through the callback. We implemented a queue of commands, and we reach a state in which we do not issue any more commands, we instead only listen to the characteristic notification.

After a random amount of time, we encounter an error in which the bluetooth service/adapter dies, in the logcat we find a DeadObjectException. It's an error that is very hard to replicate and track, but it's critical to our app. In the callback we try to handle all possible exceptions (in each one of the class methods) but the DeadObjectException cannot be handled, it seems it is thrown in another process.

The device we are using sends 2 bytes of data every time it is triggered, and can send about 20 packages per second.

We had not any luck finding help about this. Can anybody give us some advice? Is there any way to capture exceptions thrown by the bluetooth binder or the service? Which is the correct way of handling noticeable characteristics?

This is the logcat output:

    2022-02-22 13:16:50.125 5423-5910/? I/bt_stack: [INFO:gatt_main.cc(919)] gatt_data_process op_code = 27, msg_len = 4
2022-02-22 13:16:50.125 5423-5910/? E/bt_btif: bta_gattc_process_indicate, ignore HID ind/notificiation
2022-02-22 13:16:50.125 5423-5541/? E/JavaBinder: !!! FAILED BINDER TRANSACTION !!!  (parcel size = 152)
2022-02-22 13:16:50.126 5423-5541/? E/BtGatt.JNI: An exception was thrown by callback 'btgattc_notify_cb'.
2022-02-22 13:16:50.143 5423-5910/? I/bt_stack: [INFO:gatt_main.cc(919)] gatt_data_process op_code = 27, msg_len = 4
2022-02-22 13:16:50.143 5423-5910/? E/bt_btif: bta_gattc_process_indicate, ignore HID ind/notificiation
2022-02-22 13:16:50.171 17518-8879/---.---.--- I/SMNPlugin: Android [Main.cpp:763:_setStatePieceDetected()]     [Piece State Detection] PIECE_NO_CORRECT.
2022-02-22 13:16:50.180 5423-5541/? E/BtGatt.JNI: android.os.DeadObjectException: Transaction failed on small parcel; remote process probably died
        at android.os.BinderProxy.transactNative(Native Method)
        at android.os.BinderProxy.transact(BinderProxy.java:550)
        at android.bluetooth.IBluetoothGattCallback$Stub$Proxy.onNotify(IBluetoothGattCallback.java:561)
        at com.android.bluetooth.gatt.GattService.onNotify(GattService.java:1530)
2022-02-22 13:16:50.180 5423-5541/? E/JavaBinder: !!! FAILED BINDER TRANSACTION !!!  (parcel size = 152)
2022-02-22 13:16:50.180 5423-5541/? E/BtGatt.JNI: An exception was thrown by callback 'btgattc_notify_cb'.
2022-02-22 13:16:50.181 5423-5541/? E/BtGatt.JNI: android.os.DeadObjectException: Transaction failed on small parcel; remote process probably died
        at android.os.BinderProxy.transactNative(Native Method)
        at android.os.BinderProxy.transact(BinderProxy.java:550)
        at android.bluetooth.IBluetoothGattCallback$Stub$Proxy.onNotify(IBluetoothGattCallback.java:561)
        at com.android.bluetooth.gatt.GattService.onNotify(GattService.java:1530)

This is an example of our callback:

public BluetoothDeviceInterface(final BluetoothDevice device, final int arrayIndex) {
    _device = device;
    _internalArrayIndex = arrayIndex;
    _gatt = null;

    _gattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            Log.i(LOGTAG, "On Connection State Change. Status: " + status + ". New state: " + newState);
            try {
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    if (newState == BluetoothProfile.STATE_CONNECTED) {
                        Log.i(LOGTAG, "Connected to device: " + GetAddress());
                        _gatt = gatt;
                        _gatt.discoverServices();
                    } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                        Log.i(LOGTAG, "Disconnected from device: " + GetAddress());
                        _gatt.close();
                        _gatt = null;
                    }
                } else {
                    Log.i(LOGTAG, "Error connecting GATT.");
                    _gatt.close();
                    _gatt = null;
                }
                _callback.OnStateChanged(status, newState);
            } catch (Exception e) {
                Log.e(LOGTAG, "Exception in onConnectionStateChange: " + e.toString());
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            Log.i(LOGTAG, "On Services Discovered. Status: " + status);

            try {
                Log.i(LOGTAG, "Services of device " + GetAddress() + " discovered. Total number of: " + gatt.getServices().size());
                _callback.CleanServices();

                for(BluetoothGattService service : gatt.getServices()) {
                    String serviceUUID = service.getUuid().toString();

                    _callback.AddNewService(service.getType(), serviceUUID);

                    for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
                        String characteristicUUID = characteristic.getUuid().toString();
                        int characteristicProperties = characteristic.getProperties();
                        boolean isReadable = (characteristicProperties & BluetoothGattCharacteristic.PROPERTY_READ) != 0;
                        boolean isWritable = (characteristicProperties & BluetoothGattCharacteristic.PROPERTY_WRITE) != 0;
                        boolean isWritableWithoutResponse = (characteristicProperties & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0;
                        boolean isIndicateable = (characteristicProperties & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0;
                        boolean isNotifiable = (characteristicProperties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0;

                        _callback.AddNewCharacteristic(serviceUUID, characteristicUUID, isReadable, isWritable,
                                isWritableWithoutResponse, isIndicateable, isNotifiable);
                    }
                }

                _callback.OnServiceDiscoveryEnd();
            } catch (Exception e) {
                Log.e(LOGTAG, "Exception in onServicesDiscovered: " + e.toString());
            }
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            Log.i(LOGTAG, "On Characteristic Read. Status: " + status);

            try {
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    _callback.OnCharacteristicRead(characteristic.getService().getUuid().toString(), characteristic.getUuid().toString(), characteristic.getValue());
                } else if (status == BluetoothGatt.GATT_READ_NOT_PERMITTED) {
                    _callback.OnCharacteristicReadError(characteristic.getService().getUuid().toString(), characteristic.getUuid().toString(), "Read not permitted.");
                } else {
                    _callback.OnCharacteristicReadError(characteristic.getService().getUuid().toString(), characteristic.getUuid().toString(), "Status error: " + status);
                }
            } catch (Exception e) {
                Log.e(LOGTAG, "Exception in onCharacteristicRead: " + e.toString());
            }
        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            Log.i(LOGTAG, "On Characteristic Write. Status: " + status);

            try {
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    _callback.OnCharacteristicWrite(characteristic.getService().getUuid().toString(), characteristic.getUuid().toString());
                } else if (status == BluetoothGatt.GATT_WRITE_NOT_PERMITTED) {
                    _callback.OnCharacteristicWriteError(characteristic.getService().getUuid().toString(), characteristic.getUuid().toString(), "Write not permitted.");
                } else {
                    _callback.OnCharacteristicWriteError(characteristic.getService().getUuid().toString(), characteristic.getUuid().toString(), "Status error: " + status);
                }
            } catch (Exception e) {
                Log.e(LOGTAG, "Exception in onCharacteristicWrite: " + e.toString());
            }
        }

        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            Log.i(LOGTAG, "On Descriptor Write. Status: " + status);

            try {
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    _callback.OnDescriptorWrite(descriptor.getCharacteristic().getService().getUuid().toString(), descriptor.getCharacteristic().getUuid().toString());
                } else if (status == BluetoothGatt.GATT_WRITE_NOT_PERMITTED) {
                    _callback.OnDescriptorWriteError(descriptor.getCharacteristic().getService().getUuid().toString(), descriptor.getCharacteristic().getUuid().toString(), "Write not permitted.");
                } else {
                    _callback.OnDescriptorWriteError(descriptor.getCharacteristic().getService().getUuid().toString(), descriptor.getCharacteristic().getUuid().toString(), "Status error: " + status);
                }
            } catch (Exception e) {
                Log.e(LOGTAG, "Exception in onDescriptorWrite: " + e.toString());
            }
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            Log.i(LOGTAG, "On Characteristic Changed.");

            try {
                Log.i(LOGTAG, characteristic.getService().getUuid().toString() + characteristic.getUuid().toString() + characteristic.getValue().toString());
                _callback.OnCharacteristicChange(characteristic.getService().getUuid().toString(), characteristic.getUuid().toString(), characteristic.getValue());
            } catch (Exception e) {
                Log.e(LOGTAG, "Exception in onCharacteristicChanged: " + e.toString());
            }
        }

        @Override
        public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
            Log.i(LOGTAG, "On Read Remote Rssi. Status: " + status + ". Rssi: " + rssi);


            try {
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    _callback.OnRssiRead(rssi);
                } else {
                    _callback.OnRssiRead(-1);
                }
            } catch (Exception e) {
                Log.e(LOGTAG, "Exception in onReadRemoteRssi: " + e.toString());
            }
        }
    };

With an empty callback it still fails:

public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
    Log.i(LOGTAG, "On Characteristic Changed: " + characteristic.getStringValue(0));
}

This is the code we use for enabling the notifications:

public boolean EnableNotifications(String serviceUUID, String characteristicUUID) {
    if (!IsConnected())
        return false;

    BluetoothGattService service = _gatt.getService(UUID.fromString(serviceUUID));
    if (service == null)
        return false;

    BluetoothGattCharacteristic characteristic = service.getCharacteristic(UUID.fromString(characteristicUUID));
    if (characteristic == null)
        return false;

    int properties = characteristic.getProperties();
    byte[] payload;

    if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) {
        payload = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
    } else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0) {
        payload = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE;
    } else {
        return false;
    }

    BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIGURATION_UUID);
    if (descriptor == null)
        return false;

    if (!_gatt.setCharacteristicNotification(characteristic, true))
        return false;

    descriptor.setValue(payload);
    return _gatt.writeDescriptor(descriptor);
}
Luis
  • 1
  • 1
  • Try to not focus too much on logs from other processes, since it can be hard to debug Android internals anyway, and sometimes an error should just be a warning. Instead log the flow from your app, when you execute a GATT operation and when you receive a response or a notification. The post as it is written now does not contain any information we can use to debug this further. – Emil Feb 22 '22 at 17:29
  • We have literally put no code on the onCharacteristicChanged callback, but a log (as reflected on the now updated post), and we still observe the same error as stated before. Also tried to check the state of the gatt and callback variables, both before and after the error, and no object or reference is destroyed, nor any value is different. In the updated main post edit, we are going to state the new code for the callback and the code we are using for enabling the notification. – Luis Feb 23 '22 at 14:27
  • When you say "After a random amount of time, we encounter an error in which the bluetooth service/adapter dies", do you mean Bluetooth gets deactivated on the device? In that case you can use a state change receiver, see https://stackoverflow.com/questions/24888023/how-to-detect-bluetooth-state-change-using-a-broadcast-receiver. – Emil Feb 23 '22 at 20:31
  • We already were using a broadcast receiver that listened for BluetoothAdapter.ACTION_STATE_CHANGED, but it never gets called after the error. Without the error, we can turn off the Android bluetooth and the receiver gets called. But, if we reach that error state and try to disable the Android bluetooth (for a try of recovery from this error), but the app just crashes. – Luis Feb 24 '22 at 09:07
  • The random amount of time means that we are not able to replicate the error, it just happens. The device does not have any issue, nor does the plugin and the object instances it manages. It just stops working, instead of getting the callback called (onCharacteristicChanged), the DeadObject appears. – Luis Feb 24 '22 at 09:07

0 Answers0