XYOracleNetwork/sdk-ble-android

View on GitHub
ble-android-library/src/main/kotlin/network/xyo/ble/generic/gatt/server/XYBluetoothGattServer.kt

Summary

Maintainability
F
4 days
Test Coverage
package network.xyo.ble.generic.gatt.server

import android.bluetooth.*
import android.content.Context
import android.os.Build
import kotlinx.coroutines.async
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import kotlin.collections.HashMap
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import network.xyo.ble.generic.XYBluetoothBase
import network.xyo.ble.generic.gatt.peripheral.XYBluetoothResult
import network.xyo.ble.generic.gatt.peripheral.XYBluetoothResultErrorCode
import network.xyo.ble.generic.gatt.peripheral.ble

@Suppress("unused")
open class XYBluetoothGattServer(context: Context) : XYBluetoothBase(context) {
    protected val listeners = ConcurrentHashMap<String, BluetoothGattServerCallback>()
    private val services = HashMap<UUID, XYBluetoothService>()
    private val characteristics = HashMap<UUID, XYBluetoothCharacteristic>()
    private val descriptors = HashMap<UUID, XYBluetoothDescriptor>()
    private var androidGattServer: BluetoothGattServer? = null

    val devices: Array<BluetoothDevice>?
        get() = bluetoothManager?.getConnectedDevices(BluetoothProfile.GATT)?.toTypedArray()

    fun startServer(): Boolean {
        var result = false
        val bluetoothManager = this.bluetoothManager
        if (bluetoothManager?.adapter != null) {
            synchronized(this) {
                androidGattServer = bluetoothManager.openGattServer(context, primaryCallback)
                if (androidGattServer != null) {
                    result = true
                }
            }
        }
        return result
    }

    fun stopServer() {
        log.info("stopServer")
        synchronized(this) {
            androidGattServer?.close()
            androidGattServer = null
        }
    }

    fun addListener(key: String, listener: BluetoothGattServerCallback) {
        log.info("addListener")
        synchronized(listeners) {
            listeners[key] = listener
        }
    }

    fun removeListener(key: String) {
        log.info("removeListener")
        synchronized(listeners) {
            listeners.remove(key)
        }
    }

    fun getServices(): Array<BluetoothGattService> {
        log.info("getServices")
        return services.values.toTypedArray()
    }

    open fun isDeviceConnected(bluetoothDevice: BluetoothDevice): Boolean {
        log.info("isDeviceConnected")
        val connectedDevices = devices
        if (connectedDevices != null) {
            for (device in connectedDevices) {
                if (bluetoothDevice.address == device.address) {
                    return true
                }
            }
        }
        return false
    }

    suspend fun addService(serviceToAdd: XYBluetoothService) = ble.async {
        log.info("addService")
        if (androidGattServer == null) {
            return@async XYBluetoothResult<Int>(XYBluetoothResultErrorCode.NoGatt)
        }

        val addCallback = suspendCoroutine<Int> { cont ->
            addListener("addService", object : BluetoothGattServerCallback() {
                override fun onServiceAdded(status: Int, service: BluetoothGattService?) {
                    if (service?.uuid == serviceToAdd.uuid) {
                        removeListener("addService")
                        addServiceChildren(serviceToAdd)
                        cont.resume(status)
                    }
                }
            })

            androidGattServer?.addService(serviceToAdd)
        }

        services[serviceToAdd.uuid] = serviceToAdd
        return@async XYBluetoothResult(addCallback)
    }.await()

    fun removeService(uuid: UUID) {
        if (androidGattServer != null) {
            val service = services[uuid] ?: return
            androidGattServer?.removeService(service)
            services.remove(uuid)
        }
    }

    private fun addServiceChildren(serviceToAdd: XYBluetoothService) {
        for (characteristic in serviceToAdd.characteristics) {
            if (characteristic is XYBluetoothCharacteristic) {
                characteristics[characteristic.uuid] = characteristic
            }

            for (descriptor in characteristic.descriptors) {
                if (descriptor is XYBluetoothDescriptor) {
                    descriptors[descriptor.uuid] = descriptor
                }
            }
        }
    }

    private fun sendResponseWithSuccess(byteArray: ByteArray?, requestId: Int, device: BluetoothDevice?, offset: Int?) {
        if (androidGattServer != null && device != null) {
            if (isDeviceConnected(device)) {
                androidGattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset ?: 0, byteArray)
            }
        }
    }

    suspend fun waitForWrite(characteristic: BluetoothGattCharacteristic, deviceFilter: BluetoothDevice?) = ble.async {
        val response = suspendCoroutine<ByteArray?> { cont ->
            addListener("waitForWrite $characteristic", object : BluetoothGattServerCallback() {
                override fun onCharacteristicWriteRequest(device: BluetoothDevice?, requestId: Int, characteristic: BluetoothGattCharacteristic?, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray?) {
                    if (device?.address == deviceFilter?.address || deviceFilter == null) {
                        characteristic?.value = value
                        sendResponseWithSuccess(value, requestId, device, null)
                        cont.resume(value)
                    }
                }
            })
        }

        return@async XYBluetoothResult(response)
    }.await()

    suspend fun waitForRead(characteristic: BluetoothGattCharacteristic, value: ByteArray, deviceFilter: BluetoothDevice?) = ble.async {
        val response = suspendCoroutine<ByteArray?> { cont ->
            addListener("waitForRead $characteristic", object : BluetoothGattServerCallback() {
                override fun onCharacteristicReadRequest(device: BluetoothDevice?, requestId: Int, offset: Int, characteristic: BluetoothGattCharacteristic?) {
                    if (device?.address == deviceFilter?.address || deviceFilter == null) {
                        characteristic?.value = value
                        sendResponseWithSuccess(value, requestId, device, null)
                        cont.resume(value)
                    }
                }
            })
        }

        return@async XYBluetoothResult(response)
    }.await()

    suspend fun sendNotification(characteristic: BluetoothGattCharacteristic, confirm: Boolean, deviceToSend: BluetoothDevice?) = ble.async {
        if (androidGattServer != null) {
            val error = suspendCoroutine<Int> { cont ->
                val key = "sendNotification $characteristic"
                addListener(key, object : BluetoothGattServerCallback() {
                    override fun onNotificationSent(device: BluetoothDevice?, status: Int) {
                        if (device?.address == deviceToSend?.address) {
                            removeListener(key)
                            cont.resume(status)
                        }
                    }
                })

                androidGattServer?.notifyCharacteristicChanged(deviceToSend, characteristic, confirm)
            }

            return@async XYBluetoothResult(error)
        }

        return@async XYBluetoothResult<Int>(XYBluetoothResultErrorCode.NoGatt)
    }.await()

    suspend fun disconnect(deviceToDisconnectFrom: BluetoothDevice) = ble.async {
        if (androidGattServer != null) {
            val key = "disconnect $deviceToDisconnectFrom"
            val connectionState = suspendCoroutine<Int> { cont ->
                addListener(key, object : BluetoothGattServerCallback() {
                    override fun onConnectionStateChange(device: BluetoothDevice?, status: Int, newState: Int) {
                        if (deviceToDisconnectFrom.address == device?.address) {
                            removeListener(key)
                            cont.resume(newState)
                        }
                    }
                })

                androidGattServer?.cancelConnection(deviceToDisconnectFrom)
            }

            return@async XYBluetoothResult(connectionState)
        }
        return@async XYBluetoothResult<Int>(XYBluetoothResultErrorCode.NoGatt)
    }.await()

    open fun onBluetoothCharacteristicWrite(characteristic: BluetoothGattCharacteristic, device: BluetoothDevice?, value: ByteArray?): Boolean? {
        val characteristicHandler = characteristics[characteristic.uuid]
        if (characteristicHandler is XYBluetoothCharacteristic) {
            return characteristicHandler.onWriteRequest(value, device)
        }
        return null
    }

    open fun onBluetoothCharacteristicReadRequest(characteristic: BluetoothGattCharacteristic, device: BluetoothDevice?, offset: Int): XYReadRequest? {
        val characteristicHandler = characteristics[characteristic.uuid]
        if (characteristicHandler is XYBluetoothCharacteristic) {
            return characteristicHandler.onReadRequest(device, offset)
        }
        return null
    }

    open fun onBluetoothDescriptorWrite(descriptor: BluetoothGattDescriptor, device: BluetoothDevice?, value: ByteArray?): Boolean? {
        val descriptorHandler = descriptors[descriptor.uuid]
        if (descriptorHandler is XYBluetoothDescriptor) {
            return descriptorHandler.onWriteRequest(value, device)
        }
        return null
    }

    open fun onBluetoothDescriptorReadRequest(descriptor: BluetoothGattDescriptor, device: BluetoothDevice?, offset: Int): XYReadRequest? {
        val descriptorHandler = descriptors[descriptor.uuid]
        if (descriptorHandler is XYBluetoothDescriptor) {
            return descriptorHandler.onReadRequest(device, offset)
        }
        return null
    }

    class XYReadRequest(val byteArray: ByteArray, val offset: Int)

    private val primaryCallback = object : BluetoothGattServerCallback() {
        override fun onCharacteristicReadRequest(device: BluetoothDevice?, requestId: Int, offset: Int, characteristic: BluetoothGattCharacteristic?) {
            log.info("onCharacteristicReadRequest")
            super.onCharacteristicReadRequest(device, requestId, offset, characteristic)

            val service = services[characteristic?.service?.uuid]
            if (service != null && characteristic != null && device != null) {
                val readValue = onBluetoothCharacteristicReadRequest(characteristic, device, offset)
                if (readValue != null) {
                    sendResponseWithSuccess(readValue.byteArray, requestId, device, readValue.offset)
                } else {
                    log.info("Could not find response to send back.")
                    androidGattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0, null)
                    androidGattServer?.cancelConnection(device)
                }
            }

            for ((_, listener) in listeners) {
                listener.onCharacteristicReadRequest(device, requestId, offset, characteristic)
            }
        }

        override fun onCharacteristicWriteRequest(device: BluetoothDevice?, requestId: Int, characteristic: BluetoothGattCharacteristic?, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray?) {
            log.info("onCharacteristicWriteRequest $device, $characteristic")
            super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value)
            val service = services[characteristic?.service?.uuid]
            if (service != null && characteristic != null && device != null) {
                val readValue = onBluetoothCharacteristicWrite(characteristic, device, value)
                if (readValue == true) {
                    sendResponseWithSuccess(value, requestId, device, null)
                } else {
                    log.info("Could not find a responder to write to.")
                    androidGattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0, null)
                    androidGattServer?.cancelConnection(device)
                }
            }

            for ((_, listener) in listeners) {
                listener.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value)
            }
        }

        override fun onConnectionStateChange(device: BluetoothDevice?, status: Int, newState: Int) {
            log.info("onConnectionStateChange")
            super.onConnectionStateChange(device, status, newState)

            when (newState) {
                BluetoothGatt.STATE_CONNECTED -> log.info("Device connected from server: ${device?.address}")
                BluetoothGatt.STATE_DISCONNECTED -> log.info("Device disconnect from server: ${device?.address}")
            }

            for ((_, listener) in listeners) {
                listener.onConnectionStateChange(device, status, newState)
            }
        }

        override fun onDescriptorReadRequest(device: BluetoothDevice?, requestId: Int, offset: Int, descriptor: BluetoothGattDescriptor?) {
            log.info("onDescriptorReadRequest")
            super.onDescriptorReadRequest(device, requestId, offset, descriptor)

            val service = services[descriptor?.characteristic?.service?.uuid]
            if (service != null && descriptor != null && device != null) {
                val readValue = onBluetoothDescriptorReadRequest(descriptor, device, offset)
                if (readValue != null) {
                    sendResponseWithSuccess(readValue.byteArray, requestId, device, readValue.offset)
                } else {
                    log.info("Could not find response to send back.")
                    androidGattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0, null)
                    androidGattServer?.cancelConnection(device)
                }
            }

            for ((_, listener) in listeners) {
                listener.onDescriptorReadRequest(device, requestId, offset, descriptor)
            }
        }

        override fun onDescriptorWriteRequest(device: BluetoothDevice?, requestId: Int, descriptor: BluetoothGattDescriptor?, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray?) {
            log.info("onDescriptorWriteRequest")
            super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value)

            val service = services[descriptor?.characteristic?.service?.uuid]
            if (service != null && descriptor != null && device != null) {
                val readValue = onBluetoothDescriptorWrite(descriptor, device, value)
                if (readValue == true) {
                    sendResponseWithSuccess(value, requestId, device, null)
                } else {
                    log.info("Could not find a responder to write to. ${descriptor.uuid}")
                    androidGattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0, null)
                    androidGattServer?.cancelConnection(device)
                }
            }

            for ((_, listener) in listeners) {
                listener.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value)
            }
        }

        override fun onExecuteWrite(device: BluetoothDevice?, requestId: Int, execute: Boolean) {
            log.info("onExecuteWrite: $execute")
            super.onExecuteWrite(device, requestId, execute)

            sendResponseWithSuccess(byteArrayOf(), requestId, device, null)

            for ((_, listener) in listeners) {
                listener.onExecuteWrite(device, requestId, execute)
            }
        }

        override fun onNotificationSent(device: BluetoothDevice?, status: Int) {
            log.info("onNotificationSent")
            super.onNotificationSent(device, status)

            for ((_, listener) in listeners) {
                listener.onNotificationSent(device, status)
            }
        }

        override fun onServiceAdded(status: Int, service: BluetoothGattService?) {
            log.info("onServiceAdded")
            super.onServiceAdded(status, service)

            for ((_, listener) in listeners) {
                listener.onServiceAdded(status, service)
            }
        }

        override fun onMtuChanged(device: BluetoothDevice?, mtu: Int) {
            super.onMtuChanged(device, mtu)

            for ((_, listener) in listeners) {

                if (Build.VERSION.SDK_INT >= 22) {
                    listener.onMtuChanged(device, mtu)
                }
            }
        }
    }
}