Skip to content

Android: Close the GATT client when the link drops - #502

Open
tternes wants to merge 1 commit into
deviceplug:masterfrom
tternes:tt-android-gatt-close-on-drop
Open

tternes wants to merge 1 commit into
deviceplug:masterfrom
tternes:tt-android-gatt-close-on-drop

Conversation

@tternes

@tternes tternes commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

Summary

On older versions of Android, a link that drops while no command is running leaves its BluetoothGatt registered with the system. Each reconnect then registers a new client, so a device that drops and reconnects often fills Android's client table within minutes. After that every connect fails immediately with status 133, scans stop starting, and the device never comes back until the app restarts. This change closes the client when the main callback sees STATE_DISCONNECTED.

Cause

  • AdapterManager::emit evicts the peripheral on every DeviceDisconnected, so the next connection always builds a new Peripheral and a new client. The old client is never reused.
  • droidplug closes the client on an explicit disconnect, a failed connect, or a drop during a command. A drop with no command in flight wasn't covered, so that client stayed registered.

Change

In the main callback's onConnectionStateChange, after the command callback runs, close the client on STATE_DISCONNECTED if it is still this peripheral's. Paths that already closed it clear the reference first, so it's never closed twice.

Testing

Samsung Galaxy S10+ (Android 12), with one peripheral running test firmware that drops its link a few seconds after every connect, alongside up to four normal peripherals holding connections and communicating continuously.

…flight

A failed connect, an explicit disconnect, and a drop during a queued command each close the peripheral's BluetoothGatt, but a drop the app did not request while no command was running left it registered with the Bluetooth stack. AdapterManager evicts the peripheral on every DeviceDisconnected, so that client was never reused: the next connection built a new Peripheral and a new BluetoothGatt, and the orphan stayed registered. Android's GATT app table is shared with scanners and holds about 30 registrations, so a device that drops and reconnects repeatedly exhausts it. After that every registration fails with status 133, connects fail immediately, scans fail to start, and dropped devices never come back until the app process exits.

Close the client in the main callback when it reports STATE_DISCONNECTED and the pending command, if any, has not already closed it. The retry and command paths clear the reference when they close it, so a client is never closed twice.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant