简体   繁体   中英

BLE - Bluetooth GATT service can't close connection

I have implemented BLE and it works. The flow is: DrawerActivity starts, it sets a fragmentA , which has BLE implementation, because I want active BLE only in fragmentA . So if you switch to fragmentB it should terminate the BLE connection and upair the device.

What happens is that the only time it completely disconnects is, when you close the app, or turn off the bluetooth. If you close the fragmentA and open it again it works from drawerActivity . If you do it again, so this is now the 3rd time, it won't pair to the BLE device. When I investigated further, it won't even find the correct BLE device. . Meaning if you run the fragment the 4th, 5th time it is the same result.

What I want to achieve is when onDestroy in Fragment is called it should disconnect from the BLE and destroy all references. And then if you go into the fragmentA again it should recreate everything again, no matter how many times you open the fragmentA .But now the device isn't found anymore, probably because it didn't disconnect properly and BLE device has old references or something. This is how I disconnect.

This is onDestroy method:

    override fun onDestroy() {
    super.onDestroy()

    activity?.unregisterReceiver(bluetoothReceiver)
    bluetoothManager?.disconnectBluetoothService()
    bluetoothManager = null
}

And in bluetoothManager

fun disconnectBluetoothService() {
    bluetoothService?.disconnectGattServer()
}

And at the bluetoothService :

fun disconnectGattServer() {
    mConnected = false
    mBluetoothGatt?.disconnect()
    mBluetoothGatt?.close()
    mBluetoothGatt = null
}

Here are the all 3 files that are used for BLE.

FragmentA

private var bluetoothManager: MyBluetoothManager? = null

private val bluetoothReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            val action = intent.action

            if (action == BluetoothAdapter.ACTION_STATE_CHANGED) {
                when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {
                    BluetoothAdapter.STATE_OFF -> {}
                    BluetoothAdapter.STATE_ON -> {
                        initBluetoothIfPossible()
                        bluetoothManager?.scanForBluetoothDevicesIfPossible(true)
                    }
                }
            }
        }
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        listenToBluetoothChanges()
    }

    override fun onDestroy() {
        super.onDestroy()

        activity?.unregisterReceiver(bluetoothReceiver)
        bluetoothManager?.disconnectBluetoothService()
        bluetoothManager = null
    }

    private fun listenToBluetoothChanges() {
        val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
        carSharingActivity?.registerReceiver(bluetoothReceiver, filter)
    }

    private fun initBluetoothIfPossible() {
        bluetoothToken ?: return
        if (bluetoothManager != null) {
            bluetoothManager!!.pairDevice()
        } else {
            bluetoothManager = MyBluetoothManager(activity as Activity,
                    this,
                    bluetoothToken!!.token,
                    bluetoothToken!!.sessionKey,
                    bluetoothToken!!.uuid)
        }
        setImageForBluetoothStatus()
    }

MyBluetoothManager

class ACCarBluetoothManager(var activity: Activity,
                            var listener: MyBluetoothListener,
                            private var token: String,
                            private var sessionKey: String,
                            private var accessDeviceUID: String) {
        // Bluetooth adapter
        private var bluetoothAdapter: BluetoothAdapter?

        // Bluetooth service
        private var bluetoothService: MyBluetoothService? = null
        private var isBluetoothAvailable: Boolean = false

        val isBluetoothEnabled: Boolean
            get() = bluetoothAdapter?.isEnabled == true
        var connectionStatus: Boolean = false
            set(value) {
                if (field == value) return
                field = value
                if (value) stopScanning()
                else startScanning()
            }
        private var savedDevice: BluetoothDevice? = null
    /**
         * Service lifecyle management.
         */
        private val serviceConnection = object : ServiceConnection {
            override fun onServiceConnected(componentName: ComponentName, service: IBinder) {
                bluetoothService = (service as MyBluetoothService.LocalBinder).service

                bluetoothService?.isConnectedListener = { isConnected ->
                    listener.isConnected(isConnected)
                    connectionStatus = isConnected
                }

                isBluetoothAvailable = bluetoothService?.initialize() == true
            }

            override fun onServiceDisconnected(componentName: ComponentName) {
                bluetoothService = null
                connectionStatus = false
            }
        }

        /**
         * Broadcast receiver.
         */

        private val gattUpdateReceiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                try {
                    when (intent.action) {
                        BluetoothConstants.ACTION_GATT_SERVICES_DISCOVERED -> bluetoothService?.initializeIndications()
                        BluetoothConstants.ACTION_INDICATIONS_INITIALIZED -> bluetoothService?.startAuthentication(token)
                    }
                } catch (e: Exception) {
                    Log.e("GattUpdateReciever", e.message)
                }
            }
        }

        /**
         * Bluetooth device scanning callback. The scanned device is added to the list of available
         * devices.
         */
        private val bluetoothScanCallback = object : ScanCallback() {
            override fun onScanResult(callbackType: Int, result: ScanResult) {
                super.onScanResult(callbackType, result)
                val btDevice = result.device
                if (btDevice.name.isNullOrEmpty()) return

                if (deviceMatchesUID(btDevice)) {
                    savedDevice = btDevice
                    pairDevice()
                }
            }
        }  

        init {
            val gattServiceIntent = Intent(activity, MyBluetoothService::class.java)
            activity.bindService(gattServiceIntent, this.serviceConnection, Context.BIND_AUTO_CREATE)

            // Setup bluetooth adapter
            val bluetoothManager = activity.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
            bluetoothAdapter = bluetoothManager.adapter

            // If bluetooth is not enabled, request permission, otherwise start scanning process, Not IMPLEMENTED, because it is not needed.
            scanForBluetoothDevicesIfPossible()
            activity.registerReceiver(gattUpdateReceiver, BluetoothConstants.makeGattUpdateIntentFilter())
        }

        fun scanForBluetoothDevicesIfPossible(enable: Boolean = isBluetoothEnabled) {
            val hasLocationPermission = ContextCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
            if (enable) {
                if (hasLocationPermission) {
                    startScanning()
                }
                //You can request for location permission if he doesn't have permission
            } else {
                stopScanning()
            }
        }


        fun pairDevice() {
            if (isBluetoothAvailable && savedDevice != null) {
                bluetoothService?.connect(savedDevice!!)
            }
        }

        fun startScanning() {
            bluetoothAdapter?.bluetoothLeScanner?.startScan(bluetoothScanCallback)
        }

        fun stopScanning() {
            bluetoothAdapter?.bluetoothLeScanner?.stopScan(bluetoothScanCallback)
        }

        fun deviceMatchesUID(device: BluetoothDevice): Boolean {
            return device.name.equals(accessDeviceUID, ignoreCase = true)
        }
}

MyBluetoothService

class ACCarBluetoothService : Service() {
    var isConnectedListener: ((Boolean) -> Unit)? = null
    var mConnected = false
        set(value) {
            field = value
            isConnectedListener?.invoke(value)
        }
    private val mBinder = LocalBinder()
    private var mBluetoothManager: BluetoothManager? = null
    private var mBluetoothAdapter: BluetoothAdapter? = null
    private var mBluetoothGatt: BluetoothGatt? = null
    private var mDividedTokenList: MutableList<ByteArray>? = null
    // Various callback methods defined by the BLE API.
    private val mGattCallback = object : BluetoothGattCallback() {
        override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
            if (status == BluetoothGatt.GATT_FAILURE
                    || status != BluetoothGatt.GATT_SUCCESS
                    || newState == BluetoothProfile.STATE_DISCONNECTED) {
                disconnectGattServer()
                return
            }

            if (newState == BluetoothProfile.STATE_CONNECTED) {
                gatt.discoverServices()
            }

        }

        override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
            if (status == BluetoothGatt.GATT_SUCCESS) onServiceDiscoveryReady()
        }

        override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
            when {
                descriptor.characteristic.uuid == BluetoothConstants.UUID_COMMAND_CHALLENGE -> setCharacteristicNotification(
                        BluetoothConstants.UUID_DEBUG,
                        true)
                descriptor.characteristic.uuid == BluetoothConstants.UUID_DEBUG -> setCharacteristicNotification(
                        BluetoothConstants.UUID_STATUS_1,
                        true)
                descriptor.characteristic.uuid == BluetoothConstants.UUID_STATUS_1 -> setCharacteristicNotification(
                        BluetoothConstants.UUID_STATUS_2,
                        true)

                descriptor.characteristic.uuid == BluetoothConstants.UUID_STATUS_2-> setCharacteristicNotification(
                        BluetoothConstants.UUID_STATUS_3,
                        true)
                else -> onIndicationsInitialized()
            }
        }

        override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
            if (status == BluetoothGatt.GATT_SUCCESS) broadcastUpdate(characteristic)
        }

        override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
            if (characteristic.uuid == BluetoothConstants.UUID_COMMAND_CHALLENGE) {
                commandChallenge = characteristic.value
            } else {
                broadcastUpdate(characteristic)
            }
        }

        override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
            if (BluetoothConstants.UUID_AUTHORIZE_PHONE == characteristic.uuid) writeNextPartToken()
        }
    }

    override fun onBind(intent: Intent): IBinder? {
        return mBinder
    }

    /**
     * Initializes a reference to the local Bluetooth adapter.
     *
     * @return Return true if the initialization is successful.
     */
    fun initialize(): Boolean {
        if (mBluetoothManager == null) {
            mBluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
            if (mBluetoothManager == null) return false
        }

        mBluetoothAdapter = mBluetoothManager!!.adapter
        if (mBluetoothAdapter == null) return false
        return true
    }


    fun initializeIndications() {
        setCharacteristicNotification(BluetoothConstants.UUID_COMMAND_CHALLENGE, true)
    }

    fun startAuthentication(token: String) {
        mDividedTokenList = Tools.divideArray(Tools.decodeBase64(token))
        writeNextPartToken()
    }

    fun writeCommand(sessionKey: String, command: ByteArray) {
        val safeCommand = Tools.generateSafeCommand(command, commandChallenge, Tools.decodeBase64(sessionKey))
        val commandCharacteristic = mBluetoothGatt!!.getService(BluetoothConstants.UUID_CAR_CONTROL_SERVICE)
                .getCharacteristic(BluetoothConstants.UUID_COMMAND_PHONE)
        commandCharacteristic.value = safeCommand
        mBluetoothGatt!!.writeCharacteristic(commandCharacteristic)
    }

    fun connect(device: BluetoothDevice) {
        mBluetoothGatt = device.connectGatt(this, false, this.mGattCallback)
    }

    fun disconnectGattServer() {
        mConnected = false
        mBluetoothGatt?.disconnect()
        mBluetoothGatt?.close()
        mBluetoothGatt = null
    }


    private fun onIndicationsInitialized() {
        val intent = Intent()
        intent.action = BluetoothConstants.ACTION_INDICATIONS_INITIALIZED
        sendBroadcast(intent)
    }

    private fun onServiceDiscoveryReady() {
        val intent = Intent()
        intent.action = BluetoothConstants.ACTION_GATT_SERVICES_DISCOVERED
        sendBroadcast(intent)
    }

    private fun writeNextPartToken() {
        if (mDividedTokenList!!.isEmpty()) {
            broadcastUpdate(BluetoothConstants.ACTION_INIT_READY)
            return
        }
        writeValue(BluetoothConstants.UUID_AUTHORIZE_PHONE, mDividedTokenList!!.removeAt(0))
    }

    private fun broadcastUpdate(action: String) {
        val intent = Intent(action)
        sendBroadcast(intent)
    }

    private fun writeValue(characteristicUUID: UUID, valueBytes: ByteArray) {
        if (mBluetoothAdapter == null || mBluetoothGatt == null) return

        val service = mBluetoothGatt!!.getService(BluetoothConstants.UUID_CAR_CONTROL_SERVICE)
        val characteristic = service.getCharacteristic(characteristicUUID)

        characteristic.value = valueBytes
        mBluetoothGatt!!.writeCharacteristic(characteristic)
    }

    private fun setCharacteristicNotification(characteristicUUID: UUID, enabled: Boolean) {
        if (mBluetoothAdapter == null || mBluetoothGatt == null) return

        val characteristic = mBluetoothGatt!!
                .getService(BluetoothConstants.UUID_CAR_INFORMATION_SERVICE)
                .getCharacteristic(characteristicUUID)
        mBluetoothGatt!!.setCharacteristicNotification(characteristic, enabled)

        characteristic.getDescriptor(CONFIG_DESCRIPTOR)?.let {
            it.value = if (enabled) BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
            else BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
            mBluetoothGatt!!.writeDescriptor(it)
        }
    }


    private fun broadcastUpdate(characteristic: BluetoothGattCharacteristic) {
        val intent = Intent()
        if (BluetoothConstants.UUID_STATUS_1 == characteristic.uuid) {
            if (!hasDataInBluetooth(characteristic.value)) {
                mConnected = true
                statusListener?.invoke()
            }
            intent.action = BluetoothConstants.ACTION_STATUS_1_AVAILABLE
            intent.putExtra(BluetoothConstants.EXTRA_DATA, characteristic.value)
        }
        if (BluetoothConstants.UUID_DEBUG == characteristic.uuid) {
            intent.action = BluetoothConstants.ACTION_DEBUG_AVAILABLE
            intent.putExtra(BluetoothConstants.EXTRA_DATA, characteristic.value)
        }
        if (BluetoothConstants.UUID_STATUS_2 == characteristic.uuid) {
            intent.action = BluetoothConstants.ACTION_STATUS_1_AVAILABLE
            intent.putExtra(BluetoothConstants.EXTRA_DATA, characteristic.value)
        }
        if (BluetoothConstants.UUID_STATUS_3 == characteristic.uuid) {
            intent.action = BluetoothConstants.ACTION_STATUS_1_AVAILABLE
            intent.putExtra(BluetoothConstants.EXTRA_DATA, characteristic.value)
        }

        sendBroadcast(intent)
    }

    private fun hasDataInBluetooth(byteArray: ByteArray): Boolean {
        for (b in byteArray) {
            if (b.toInt() != 0) {
                return false
            }
        }
        return true
    }

    inner class LocalBinder : Binder() {
        val service: MyBluetoothService
            get() = this@MyBluetoothService
    }

}

I found the solution. The problem vas in:

     fun pairDevice() {
        if (isBluetoothAvailable && savedDevice != null) {
            bluetoothService?.connect(savedDevice!!)
        }
    }

Because it was trying to connect again and again it stopped broadcasting. I solved it with:

fun pairDevice() {
    if (isConnected) return
    if (isBluetoothAvailable && savedDevice != null) {
        bluetoothService?.connect(savedDevice!!)
        isConnected = true
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM