Skip to content

Instantly share code, notes, and snippets.

@tir38
Last active May 16, 2019 21:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tir38/225b92893e1e91cc9381eec96ca7dd16 to your computer and use it in GitHub Desktop.
Save tir38/225b92893e1e91cc9381eec96ca7dd16 to your computer and use it in GitHub Desktop.
We were badly using reflection to spy on android.bluetooth.BluetoothGatt and see while it would fail to write. Don't do this. Instead vote to have methods added/updated to use public API https://issuetracker.google.com/issues/132907122
package com.example
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Class to help debug GATT issues
*/
final class GattDebugger {
private GattDebugger() {
// can't instantiate helper class
}
/**
* Debug reason why {@link BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic)} failed. BluetoothGatt
* is not very helpful in explaining *why* it returned false when trying to writeCharacteristic. We can use
* reflection to try to spy on BluetoothGatt to see if we can figure out why it returned false. This method isn't
* super useful, but it might provide us a bit of insight to help debug problems.
*
* @param bluetoothGatt Gatt client that failed
* @param characteristic Gatt Characteristic that failed
* @return String explaining possible reason
*/
static String getFailingWriteCharacteristicReason(BluetoothGatt bluetoothGatt, BluetoothGattCharacteristic
characteristic) {
// check properties
if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) == 0
&& (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == 0) {
return "Characteristic properties are neither PROPERTY_WRITE nor PROPERTY_WRITE_NO_RESPONSE";
}
// check mService == null
try {
Field serviceField;
serviceField = bluetoothGatt.getClass().getDeclaredField("mService");
serviceField.setAccessible(true);
// use object since service is of type IBluetoothGatt which we don't have access to. It's no big deal
// since all we need to do is null check it
Object service = serviceField.get(bluetoothGatt);
if (service == null) {
return "service is null";
}
} catch (NoSuchFieldException | IllegalAccessException | ClassCastException e) {
// eat it since this is just logging
}
// check mClientIf == 0
try {
Field clientIfField;
clientIfField = bluetoothGatt.getClass().getDeclaredField("mClientIf");
clientIfField.setAccessible(true);
int mClientIf = (int) clientIfField.get(bluetoothGatt);
if (mClientIf == 0) {
return "mClientIf == 0";
}
} catch (NoSuchFieldException | IllegalAccessException | ClassCastException e) {
// eat it since this is just logging
}
// check characteristic.getValue() is null
if (characteristic.getValue() == null) {
return "characteristic.getValue() == null";
}
// check characteristic.getService() is null
BluetoothGattService service = characteristic.getService();
if (service == null) {
return "characteristic.getService() == null";
}
// check service.getDevice() is null
try {
@SuppressLint("PrivateApi")
Method method = service.getClass().getDeclaredMethod("getDevice");
method.setAccessible(true);
BluetoothDevice bluetoothDevice = (BluetoothDevice) method.invoke(service);
if (bluetoothDevice == null) {
return "Bluetooth device is null";
}
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
// eat it since this is just logging
}
// check mDeviceBusy
try {
Field deviceBusyField;
deviceBusyField = bluetoothGatt.getClass().getDeclaredField("mDeviceBusy");
deviceBusyField.setAccessible(true);
Boolean mDeviceBusy = (Boolean) deviceBusyField.get(bluetoothGatt);
if (mDeviceBusy) {
return "BluetoothGatt is busy";
}
} catch (NoSuchFieldException | IllegalAccessException e) {
// eat it since this is just logging
}
return "unknown; check logs for possible exception";
}
/**
* Debug reason why {@link BluetoothGatt#writeDescriptor(BluetoothGattDescriptor)} failed. BluetoothGatt
* is not very helpful in explaining *why* it returned false when trying to writeDescriptor. We can use
* reflection to try to spy on BluetoothGatt to see if we can figure out why it returned false. This method isn't
* super useful, but it might provide us a bit of insight to help debug problems.
*
* @param bluetoothGatt Gatt client that failed
* @param descriptor Gatt Descriptor that failed to write
* @return String explaining possible reason
*/
static String getFailingWriteDescriptorReason(BluetoothGatt bluetoothGatt,
BluetoothGattDescriptor descriptor) {
// check mService == null
try {
Field serviceField;
serviceField = bluetoothGatt.getClass().getDeclaredField("mService");
serviceField.setAccessible(true);
// use object since service is of type IBluetoothGatt which we don't have access to. It's no big deal
// since all we need to do is null check it
Object service = serviceField.get(bluetoothGatt);
if (service == null) {
return "service is null";
}
} catch (NoSuchFieldException | IllegalAccessException | ClassCastException e) {
// eat it since this is just logging
}
// check mClientIf == 0
try {
Field clientIfField;
clientIfField = bluetoothGatt.getClass().getDeclaredField("mClientIf");
clientIfField.setAccessible(true);
int mClientIf = (int) clientIfField.get(bluetoothGatt);
if (mClientIf == 0) {
return "mClientIf == 0";
}
} catch (NoSuchFieldException | IllegalAccessException | ClassCastException e) {
// eat it since this is just logging
}
// check descriptor.getValue() is null
if (descriptor.getValue() == null) {
return "descriptor.getValue() == null";
}
// check descriptor.getCharacteristic() is null
BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
if (characteristic == null) {
return "descriptor.getCharacteristic() == null";
}
// check characteristic.getService() is null
BluetoothGattService service = characteristic.getService();
if (service == null) {
return "characteristic.getService() == null";
}
// check service.getDevice() is null
try {
@SuppressLint("PrivateApi")
Method method = service.getClass().getDeclaredMethod("getDevice");
method.setAccessible(true);
BluetoothDevice bluetoothDevice = (BluetoothDevice) method.invoke(service);
if (bluetoothDevice == null) {
return "Bluetooth device is null";
}
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
// eat it since this is just logging
}
// check mDeviceBusy
try {
Field deviceBusyField;
deviceBusyField = bluetoothGatt.getClass().getDeclaredField("mDeviceBusy");
deviceBusyField.setAccessible(true);
Boolean mDeviceBusy = (Boolean) deviceBusyField.get(bluetoothGatt);
if (mDeviceBusy) {
return "BluetoothGatt is busy";
}
} catch (NoSuchFieldException | IllegalAccessException e) {
// eat it since this is just logging
}
return "unknown; check logs for possible exception";
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment