BLE advertising and scanning are pretty painless thanks to the BLESSED library
Some devices (eg Samsung J5 2016) receive the advertising packet but not the scan record, so if we want to provide extra information in the scan record we also need to make this information available via GATT
BLESSED doesn't provide a wrapper for the new advertising set API, which supports new Bluetooth 5 advertising features such as periodic advertising, extended advertising and coded PHY, so if we want to use those features we'll need to use the Android API directly
If we use any Bluetooth 5 advertising features then we also need to use the Android API for scanning (or submit a pull request upstream to allow legacy mode to be turned off for scanning)
We can, however, use BLESSED to request coded PHY for GATT connections
BLESSED also makes it relatively painless to send and receive information via GATT characteristics
Some devices allow connections to multiple peripherals when acting as a central
Some devices also support connections from multiple centrals when acting as a peripheral, but since GATT doesn't provide a way to send data to a specific central we'd need to do some creative thinking to make use of this
L2CAP connection-oriented channels (CoC) seem to work as advertised. The server socket provides a PSM (something like a dynamic port number) that needs to be communicated to the client somehow (eg via BLE advertising). We can use L2CAP when the central and peripheral both support it and fall back to GATT when they don't
The spurious GATT callbacks also happen when not using L2CAP, if both devices are operating as centrals and peripherals simultaneously. When device A (acting as central) connects to device B (acting as peripheral), device A receives a spurious callback to onCentralConnected(), without a matching call to onCentralDisconnected() when the devices disconnect.
This breaks the mechanism I came up with for preventing multiple centrals from trying to communicate with the same peripheral by publishing a "busy characteristic" containing the number of connected centrals. So we'll need to resolve or work around this before continuing to test advertising and scannig on a wide range of devices.
Three of the test devices (Huawei Y6P, Samsung Galaxy A21s and Sony XZ2) support extended advertising according to BluetoothAdapter#isLeExtendedAdvertisingSupported(). On the Huawei Y6P, getLeMaximumAdvertisingDataLength() returns 192, while on the other two devices it reports 1650, which is the max allowed by the spec.
I modified BLESSED to allow the legacy flag to be toggled for scanning. When this flag is set to false, non-legacy scan results should be reported along with legacy results.
When the advertisement type is set to scannable, none of the devices can see each other.
When the advertisement is not scannable, the service data needs to be included in the advertisement itself rather than the scan record, which reduces the available space by 18 bytes. However, this allows the advertisement to be connectable (Android doesn't allow extended advertisements to be both connectable and scannable, although as far as I can see the spec allows this).
Although two of the test devices claim to support up to 1650 bytes of data, we can't use more than 274 bytes in practice, otherwise the Samsung and the Sony fail to receive the scan result, while the Huawei only receives part of the service data.
(If we exceed this limit by 1-17 bytes then the Huawei fails to receive the scan result too, while if we exceed it by exactly 18 bytes then it receives the result with no service data. This might point to arithmetic bugs when reassembling chained advertisement packets.)
We need to subtract 18 bytes from the available space for the service UUID that allows the scan to filter by UUID, and another 18 bytes for the same service UUID to introduce the service data. So the usable payload is 238 bytes for the Samsung and the Sony, or 156 bytes for the Huawei.
It's possible that advertisements with more data are in fact being received but they're not making their way up through the API and the BLESSED library.
We can squeeze 14 more bytes of payload into the advertisement packet by storing the data under a 16-bit manufacturer UUID (eg 0xFCF1 for Google, see assigned numbers spec) rather than a 128-bit service UUID.
Some results from testing with older devices (Moto G 4G, Huawei P8 Lite 2015, Samsung J5 2016):
The P8 Lite doesn't support advertising (BluetoothAdapter#getBluetoothLeAdvertiser() returns null)
The Moto G never receives the result of starting advertising (no callback to onAdvertisingStarted() or onAdvertiseFailure())
The Moto G and P8 Lite can discover the J5, but it can't discover them and they can't discover each other
The P8 Lite can connect to the J5 via GATT and exchange pings and pongs
The Moto G can't connect to the J5 via GATT: it receives a callback to onConnectingPeripheral(), never receives onConnectedPeripheral(), and eventually receives onDisconnectedPeripheral() with status LMP_OR_LL_RESPONSE_TIMEOUT. The J5 receives onCentralConnected() and onCentralDisconnected() as expected. The J5 can subsequently connect back to the Moto G, using the address it learned from the unsuccessful connection, and exchange pings and pongs
When the J5 receives a connection from the Moto G or P8 Lite, the J5 sees the same address that the Moto G/P8 Lite gets from its own BluetoothAdapter, ie the BT classic address. So it may be possible for the Moto G/P8 Lite to advertise this address via WFD (since BLE advertising isn't available) and use the address to receive GATT connections and/or BT classic connections.