0004 — No hidden, privileged or reflected platform APIs¶
- Status: accepted
- Date: 2026-09-21
- Deciders: BlueLib maintainers
Context¶
Bluetooth on Android has a long tradition of hidden APIs that would make the library more capable:
BluetoothDevice.removeBond(), BluetoothDevice.requestMtu(), BluetoothGatt.refresh(),
BluetoothAdapter.enable()/disable(), the BluetoothA2dp internals, leMaximumAdvertisingDataLength
before it was public.
Applications that use them hit three walls: NoSuchMethodException on some OEM builds, silent blocking
by the platform's hidden-API enforcement, and the Play Store's restrictions on privileged operations.
Decision¶
BlueLib uses only the public SDK surface, and when something is unavailable it says so explicitly instead of reaching around it:
| Want | Reality | BlueLib's answer |
|---|---|---|
| Unbond a device | BluetoothDevice.removeBond() is still not public |
unbond() uses CompanionDeviceManager.removeBond(associationId) on Android 13+, otherwise a typed error explaining the association requirement |
| Clear the GATT service cache | BluetoothGatt.refresh() is hidden |
GattStatus.GATT_DATABASE_OUT_OF_SYNC is retried as a service rediscovery, which is the supported path |
| Enable or disable Bluetooth | BluetoothAdapter.enable() is deprecated and privileged |
enableBluetoothIntent() returns ACTION_REQUEST_ENABLE for the app to launch |
| Request a higher MTU from the GATT server | No such API exists in the server role | GattServer.requestMtu fails with FeatureUnsupported explaining that the central owns the exchange, and BlueLib reports the negotiated value from onMtuChanged |
| Read a device's MAC address on Android 12+ | Available, but with BLUETOOTH_CONNECT |
The permission gateway asks for it up front instead of letting SecurityException surface |
Consequences¶
Good
- The library behaves predictably across OEM builds, because it never depends on an implementation detail that a vendor may have changed.
- No
SecurityExceptionsurprises, no Play policy problems, no reflection that R8 can break. - Every gap becomes documentation: the Roadmap lists what is impossible today and why.
Bad
- Some things applications might want are simply not offered (clearing the GATT cache on demand, forcing an unpair for a device the app is not associated with).
CompanionDeviceManagerassociation is required for unbonding, which is an extra flow for apps that want it.
Alternatives considered¶
- Reflection with a graceful fallback. Rejected: it produces OEM-dependent behaviour, which is precisely the bug class BlueLib exists to remove.
- Shipping a
@RequiresSystemAppmodule. Rejected as out of scope for a general purpose library; privileged features such as LE Audio broadcast source control belong to a system app.