I have implemented duplex BLE communication (essentially, something like serial TX/RX cable) using two ATT characteristics on the peripheral - one is writable, the other is
You are correct.
If you use "write with response" then the onCharacteristicWrite callback will be called after the remote sends back a write response. If you use "write without response" however, Android has a flow control mechanism which buffers the data you write. It works like this:
The app writes a packet. The Bluetooth stack puts this packet in its internal buffer. If there is still more space in the buffer after enqueuing this packet, the onCharacteristicWrite callback is called, so you can immediately write another packet. When the buffer is full, it waits to call the onCharacteristicWrite callback until there is now 50% space (if I remember correctly) available in the buffer.
As long as there are packets in the internal buffer, it tries to write them to the Bluetooth controller, which also has a limited number of packets that can be buffered. The Bluetooth controller sends a "number of packets complete" event back to Android's Bluetooth stack over HCI which indicates that the Link Layer of the remote's device has acknowledged a packet. (This doesn't indicate that the remote app hasn't received it; only that the remote Bluetooth controller received it.) If there is no space available in the Bluetooth controller, it will wait until there is space.
This is much smarter than how it works on iOS. There if you send a lot of "write with response" packets, they will be discarded before they are even sent if the internal buffer is full. (This can be solved by sending "write with response" each 10th packet or so).
Unfortunately (for your case), Android's Bluetooth stack does not send a callback to the app when a packet has been acknowledged by the remote Link Layer, so you will have to base your speed on the onCharacteristicWrite callback. You could however send status notifications in the other direction each 10th packet or so, which I think will give you good results. If Android's Bluetooth stack would instead send the onCharacteristicWrite callback when the Link Layer acknowledgement is received, that would reduce the speed to only one packet per connection event.
If the link sometimes get disconnected due to supervision timeout, you should be aware of the bug report I posted some time ago: https://issuetracker.google.com/issues/37121017.
About your questions with properties/permissions, that could have been the same thing but the Bluetooth team decided to separate those. The permissions kind of relate to the ATT protocol, telling the Bluetooth stack what a client is allowed to do. The properties are just some properties that the other side can read in order to know what type of characteristic it is.