Last active
May 16, 2019 21:12
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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