androidmtu

Is the default MTU for Android 14 now 517 bytes?


Is the default MTU for Android 14 now 517 bytes or is it still 23 bytes? If the former, it may be problematic for Bluetooth chips that lack MTU negotiation.

https://developer.android.com/about/versions/14/behavior-changes-all

~I now understand that the default MTU value (23) has not changed under Android 14. Thank you to all who provided feedback and trust that others will find utility in the commentary.


Solution

  • The specification only allows one MTU negotiation per connection. Previously, every client holding a BluetoothGatt object could request its own MTU. Android previously ignored the spec and re-negotiated the MTU on every MTU request. Allowing custom MTUs does not play well in a shared environment, where multiple independent apps talks to the same BLE device. If one app first negotiates MTU=25 since it does not need more, but the other app wants to (later) negotiate MTU=500, it can't do that if the spec is to be followed.

    Therefore, now Android selects an MTU which should be good enough for most use cases (517) the first time a client wants to negotiate MTU. An MTU of 517 makes sure the longest GATT value (512 bytes) can be sent in one L2CAP packet for most types of commands/requests/notifications.

    I don't see why this would break pre 4.2 BLE devices. Nothing about the MTU or GATT packet lengths has been changed in any version of BLE. Devices that don't support a higher MTU than the default (23) will keep using 23 and devices that support a higher MTU than 23 but lower than 517 will negotiate that value.

    Edit:

    I decided to test what happens when the remote device does not support MTU Exchange. Per the specification (all BLE versions), an error response should be sent to an Exchange MTU Request, containing the error code "Request Not Supported".

    When the Android app calls requestMtu with some arbitrary MTU as parameter, see this btmon log what packets are exchanged:

    > ACL Data RX: Handle 3585 flags 0x02 dlen 7
          ATT: Exchange MTU Request (0x02) len 2
            Client RX MTU: 517
    < ACL Data TX: Handle 3585 flags 0x00 dlen 9
          ATT: Error Response (0x01) len 4
            Exchange MTU Request (0x02)
            Handle: 0x0000
            Error: Request Not Supported (0x06)
    

    The callback onMtuChanged is then called, with the parameters mtu=23 and status=6 (Request Not Supported). The MTU therefore continues to be the default (23).

    However, I noticed a bug in Android (Pixel 7 running Android 14) that happens if any BluetoothGatt client tries to call requestMtu after a previous requestMtu called by any BluetoothGatt client (possibly another) has resulted in the error "Request Not Supported". At this point, no onMtuChanged callback will be called, and no other GATT operations seem to work on this BluetoothGatt object, e.g. calling readCharacteristic never sends any Read Request nor is any response callback called. This bad state is present until the device disconnects.