I'm implementing a small BLE app and I have a list of devices and I want to turn them off when I press on them in the list view.
My service UUID is:0xFFB0
My characteristic UUID is:0xFFB7 Write with response/ Read with response
Characteristic format:
Byte order ; 0 ; 1~19
R/W ON/OFF ; -
0x01 -> ON and 0x00 -> OFF
class MainActivity : AppCompatActivity() {
private var macList: ArrayList<String> = ArrayList()
private var deviceList: ArrayList<BluetoothDevice> = ArrayList()
private lateinit var adapter: ArrayAdapter<String>
private var mBtAdapter: BluetoothAdapter? = null
private var mConnectedGatt: BluetoothGatt? = null
private var mCharacteristic: BluetoothGattCharacteristic? = null
private var on = true
private lateinit var currentDevice: BluetoothDevice
/**
* CHECK IF BT AND LOCATION IS ON!
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (!isBLESupported(this)) {
Toast.makeText(this, "This device doesn't support bluetooth", Toast.LENGTH_SHORT).show()
finish()
} else {
if (!mBtAdapter!!.isEnabled) {
val enableBluetoothIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
startActivityForResult(enableBluetoothIntent, REQUEST_ENABLE_BLUETOOTH)
}
}
adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, macList)
list_view.adapter = adapter
list_view.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
currentDevice = deviceList[position]
mConnectedGatt = currentDevice.connectGatt(applicationContext, false, gattCallback)
}
scan_button.setOnClickListener {
scanForDeviceWithFilter(LIGHT_SERVICE)
}
power_button.setOnClickListener {
mConnectedGatt = currentDevice.connectGatt(applicationContext, true, powerCallback)
on = !on
}
}
override fun onStop() {
super.onStop()
//Disconnect from any active tag connection
if (mConnectedGatt != null) {
mConnectedGatt!!.disconnect()
mConnectedGatt = null
}
}
private fun scanForDeviceWithFilter(serviceUUID: Int) {
val uuid = ParcelUuid(convertFromInteger(serviceUUID))
val filter = ScanFilter.Builder().setServiceUuid(uuid).build()
val filters = listOf(filter)
val settings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
checkBTPermissions()
mBtAdapter!!.bluetoothLeScanner.startScan(filters, settings, scanDevicesCallback)
Thread.sleep(3000)
mBtAdapter!!.bluetoothLeScanner.stopScan(scanDevicesCallback)
}
private val scanDevicesCallback = object : ScanCallback() {
override fun onBatchScanResults(results: MutableList<ScanResult>?) {
results?.forEach { result ->
macList.add(result.device.toString())
Log.d(TAG, "device name:${result.device}")
}
}
override fun onScanResult(callbackType: Int, result: ScanResult?) {
result?.let {
if (!macList.contains(result.device.name.toString())) {
deviceList.add(result.device)
macList.add(result.device.name.toString())
adapter.notifyDataSetChanged()
}
Log.d(TAG, "device found:${result.device}")
}
}
override fun onScanFailed(errorCode: Int) {
Log.d(TAG, "Scan failed $errorCode")
}
}
private val gattCallback = object : BluetoothGattCallback() {
/* OK */
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
Log.d(TAG, "Connection State Change: " + status + " -> " + connectionState(newState))
if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
/**
* Once successfully connected, we must next discover all the services on the
* device before we can read and write their characteristics.
*/
gatt.discoverServices()
Thread.sleep(500)
} else if (status != BluetoothGatt.GATT_SUCCESS) {
/**
* If there is a failure at any stage, simply disconnect
*/
gatt.disconnect()
}
}
private fun connectionState(status: Int): String {
return when (status) {
BluetoothProfile.STATE_CONNECTED -> "Connected"
BluetoothProfile.STATE_DISCONNECTED -> "Disconnected"
BluetoothProfile.STATE_CONNECTING -> "Connecting"
BluetoothProfile.STATE_DISCONNECTING -> "Disconnecting"
else -> status.toString()
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
mCharacteristic = gatt?.getService(convertFromInteger(LIGHT_SERVICE))?.getCharacteristic(convertFromInteger(PASSWORD_CHARACTERISTIC))
mCharacteristic!!.setValue("0123")
if (gatt!!.writeCharacteristic(mCharacteristic)) {
Log.d(TAG, "Login success")
Thread.sleep(500)
} else
Log.d(TAG, "Login failed")
}
override fun onCharacteristicWrite(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?, status: Int) {
if (status != BluetoothGatt.GATT_SUCCESS) {
Log.d("onCharacteristicWrite", "Failed write, retrying: $status")
gatt!!.writeCharacteristic(characteristic)
}
super.onCharacteristicWrite(gatt, characteristic, status)
}
}
private val powerCallback = object : BluetoothGattCallback() {
/* OK */
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
Log.d(TAG, "Connection State Change: " + status + " -> " + connectionState(newState))
if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices()
Thread.sleep(500)
} else if (status != BluetoothGatt.GATT_SUCCESS) {
gatt.disconnect()
}
}
private fun connectionState(status: Int): String {
return when (status) {
BluetoothProfile.STATE_CONNECTED -> "Connected"
BluetoothProfile.STATE_DISCONNECTED -> "Disconnected"
BluetoothProfile.STATE_CONNECTING -> "Connecting"
BluetoothProfile.STATE_DISCONNECTING -> "Disconnecting"
else -> status.toString()
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
if (on) {
mCharacteristic = gatt!!.getService(convertFromInteger(LIGHT_SERVICE))?.getCharacteristic(convertFromInteger(LIGHT_CHARACTERISTIC))
mCharacteristic!!.setValue("0")
gatt.setCharacteristicNotification(mCharacteristic, true)
if (gatt.writeCharacteristic(mCharacteristic)) {
Log.d(TAG, "Power off success")
Thread.sleep(500)
} else Log.d(TAG, "Power off failed")
} else {
mCharacteristic = gatt!!.getService(convertFromInteger(LIGHT_SERVICE))?.getCharacteristic(convertFromInteger(LIGHT_CHARACTERISTIC))
mCharacteristic!!.setValue("1")
if (gatt.writeCharacteristic(mCharacteristic)) {
Log.d(TAG, "Power on success")
Thread.sleep(500)
} else Log.d(TAG, "Power on failed")
}
}
override fun onCharacteristicWrite(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?, status: Int) {
if (status != BluetoothGatt.GATT_SUCCESS) {
Log.d("onCharacteristicWrite", "Failed write, retrying: $status")
gatt!!.writeCharacteristic(characteristic)
}
super.onCharacteristicWrite(gatt, characteristic, status);
}
}
private fun isBLESupported(context: Context): Boolean {
return BluetoothAdapter.getDefaultAdapter() != null && context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
}
init {
mBtAdapter = BluetoothAdapter.getDefaultAdapter()
}
private fun checkBTPermissions() {
val permissionCheck = checkSelfPermission("Manifest.permission.ACCESS_FINE_LOCATION")
if (permissionCheck != 0) {
requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 1001)
}
}
fun convertFromInteger(i: Int): UUID? {
val msb = 0x0000000000001000L
val lsb = -0x7fffff7fa064cb05L
val value = (i and ((-0x1).toLong()).toInt()).toLong()
return UUID(msb or (value shl 32), lsb)
}
companion object {
private const val TAG = "Main Activity"
private const val LIGHT_SERVICE = 0xffb0
private const val LIGHT_CHARACTERISTIC = 0xffb7
private const val PASSWORD_CHARACTERISTIC = 0xffba
private const val REQUEST_ENABLE_BLUETOOTH = 1
}
}
I changed my code, but now when I try to write I get status = 128; GATT_NO_RESOURCES and I have no clue what to do.
I'm not sure if you have understand the flow correctly.
The onCharacteristicWrite is not called "to send data to the BLE device". It is being called after you have called writeCharacteristic, after the remote device has responded.
This callback is called when you are trying to send data using writeCharacteristic(characteristics) and the BLE device responds with some value.
This is not really correct about onCharacteristicChanged. This method is called whenever the local device receives a notification from the remote device. To enable this you must first tell the local Bluetooth stack to forward the notifications to you by calling setCharacteristicNotification first, and write the client characteristic configuration descriptor to the remote device so it will send notifications.
After you have called writeDescriptor, the onDescriptorWrite callback will be called when the remote device responds.
What you should do is to implement the onServicesDiscovered method which will be called as a result of discoverServices, when the service discovery is complete. In this callback you can call writeCharacteristic on the ON/OFF characteristic.
From your description it doesn't seem you need to receive notifications.
I solved my problem. I was sending a string to the device, not a byte value.
mCharacteristic!!.value = byteArrayOf(0x00) fixed my problem.
Related
i am trying to set notification callback in BLE nordic, where i am using the Android BLE library (Nordic Github). But i not ablet to get the notification event when i am changing the value of the characteristics.
`
class BleManagerHP1T(context: Context) : BleManager(context) {
override fun getGattCallback(): BleManagerGattCallback = GattCallback()
override fun log(priority: Int, message: String) {
if (BuildConfig.DEBUG || priority == Log.ERROR) {
Log.println(priority, GattService.TAG, message)
}
}
private inner class GattCallback : BleManagerGattCallback() {
private var myCharacteristic: BluetoothGattCharacteristic? = null
private var rxCharacteristic: BluetoothGattCharacteristic? = null
#SuppressLint("MissingPermission")
override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean {
val service = gatt.getService(GattService.MyServiceProfile.MY_SERVICE_UUID)
myCharacteristic =
service?.getCharacteristic(GattService.MyServiceProfile.MY_CHARACTERISTIC_UUID)
val myCharacteristicProperties = myCharacteristic?.properties ?: 0
Log.d(TAG, "isRequiredServiceSupported: notify ${(myCharacteristicProperties and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0)}")
rxCharacteristic = service?.getCharacteristic(GattService.MyServiceProfile.RX_CHARACTERISTIC_UUID)
val obj = JSONObject()
obj.put("OPCODE","PROVISION")
rxCharacteristic?.value = obj.toString().encodeToByteArray()
val rxRead = gatt.writeCharacteristic(rxCharacteristic)
Log.d(TAG, "isRequiredServiceSupported: Read $rxRead")
return (myCharacteristicProperties and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0)
}
override fun initialize() {
enableNotifications(myCharacteristic).enqueue()
requestMtu(260).enqueue();
setNotificationCallback(myCharacteristic).with { _, data ->
Log.d(TAG, "initialize: TX char Notification Called")
if (data.value != null) {
val value = String(data.value!!, Charsets.UTF_8)
Log.d(TAG, "initialize: TX char value $value")
}
}
requestMtu(260).enqueue();
enableNotifications(rxCharacteristic).enqueue()
setNotificationCallback(rxCharacteristic).with { _, data ->
Log.d(TAG, "initialize: RX char Notification Called")
if (data.value != null) {
val value = String(data.value!!, Charsets.UTF_8)
Log.d(TAG, "initialize: RX char value $value")
}
}
beginAtomicRequestQueue()
.add(enableNotifications(myCharacteristic)
.fail { _: BluetoothDevice?, status: Int ->
log(Log.ERROR, "Could not subscribe: $status")
disconnect().enqueue()
}
)
.done {
log(Log.INFO, "Target initialized")
}
.enqueue()
}
override fun onServicesInvalidated() {
myCharacteristic = null
}
}
override fun readCharacteristic(characteristic: BluetoothGattCharacteristic?): ReadRequest {
return Request.newReadRequest(characteristic)
}
}
`
gatt connection is establishing perfectly fine, using this code.
`
val bleManager = BleManagerHP1T(this#ControllerActivity)
synchronized (this) {
bleManager.connect(deviceMainList[position]).useAutoConnect(false).enqueue()
}
`
here is the gatt service file .
`
class GattService : Service() {
private val defaultScope = CoroutineScope(Dispatchers.Default)
private lateinit var bluetoothObserver: BroadcastReceiver
private var myCharacteristicChangedChannel: SendChannel<String>? = null
private val clientManagers = mutableMapOf<String, ClientManager>()
// val connect = BleManager()
#RequiresApi(Build.VERSION_CODES.O)
override fun onCreate() {
super.onCreate()
// Setup as a foreground service
val notificationChannel = NotificationChannel(
GattService::class.java.simpleName,
resources.getString(R.string.gatt_service_name),
NotificationManager.IMPORTANCE_DEFAULT
)
val notificationService =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationService.createNotificationChannel(notificationChannel)
val notification = NotificationCompat.Builder(this, GattService::class.java.simpleName)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(resources.getString(R.string.gatt_service_name))
.setContentText(resources.getString(R.string.gatt_service_running_notification))
.setAutoCancel(true)
startForeground(1, notification.build())
// Observe OS state changes in BLE
bluetoothObserver = object : BroadcastReceiver() {
#SuppressLint("MissingPermission")
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
BluetoothAdapter.ACTION_STATE_CHANGED -> {
val bluetoothState = intent.getIntExtra(
BluetoothAdapter.EXTRA_STATE,
-1
)
when (bluetoothState) {
BluetoothAdapter.STATE_ON -> enableBleServices()
BluetoothAdapter.STATE_OFF -> disableBleServices()
}
}
BluetoothDevice.ACTION_BOND_STATE_CHANGED -> {
val device =
intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
Log.d(TAG, "Bond state changed for device ${device?.address}: ${device?.bondState}")
when (device?.bondState) {
BluetoothDevice.BOND_BONDED -> addDevice(device)
BluetoothDevice.BOND_NONE -> removeDevice(device)
}
}
}
}
}
registerReceiver(bluetoothObserver, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED))
registerReceiver(bluetoothObserver, IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED))
// Startup BLE if we have it
val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
if (bluetoothManager.adapter?.isEnabled == true) enableBleServices()
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(bluetoothObserver)
disableBleServices()
}
override fun onBind(intent: Intent?): IBinder? =
when (intent?.action) {
DATA_PLANE_ACTION -> {
DataPlane()
}
else -> null
}
override fun onUnbind(intent: Intent?): Boolean =
when (intent?.action) {
DATA_PLANE_ACTION -> {
myCharacteristicChangedChannel = null
true
}
else -> false
}
/**
* A binding to be used to interact with data of the service
*/
inner class DataPlane : Binder() {
fun setMyCharacteristicChangedChannel(sendChannel: SendChannel<String>) {
myCharacteristicChangedChannel = sendChannel
}
}
#SuppressLint("MissingPermission")
private fun enableBleServices() {
val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
if (bluetoothManager.adapter?.isEnabled == true) {
Log.i(TAG, "Enabling BLE services")
bluetoothManager.adapter.bondedDevices.forEach { device -> addDevice(device) }
} else {
Log.w(TAG, "Cannot enable BLE services as either there is no Bluetooth adapter or it is disabled")
}
}
private fun disableBleServices() {
clientManagers.values.forEach { clientManager ->
clientManager.close()
}
clientManagers.clear()
}
private fun addDevice(device: BluetoothDevice) {
if (!clientManagers.containsKey(device.address)) {
val clientManager = ClientManager()
clientManager.connect(device).useAutoConnect(true).enqueue()
clientManagers[device.address] = clientManager
}
}
private fun removeDevice(device: BluetoothDevice) {
clientManagers.remove(device.address)?.close()
}
/*
* Manages the entire GATT service, declaring the services and characteristics on offer
*/
companion object {
/**
* A binding action to return a binding that can be used in relation to the service's data
*/
const val DATA_PLANE_ACTION = "data-plane"
const val TAG = "gatt-service"
}
private inner class ClientManager : BleManager(this#GattService) {
override fun getGattCallback(): BleManagerGattCallback = GattCallback()
override fun log(priority: Int, message: String) {
if (BuildConfig.DEBUG || priority == Log.ERROR) {
Log.println(priority, TAG, message)
Log.d(TAG, "log: $message")
}
}
private inner class GattCallback : BleManagerGattCallback() {
private var myCharacteristic: BluetoothGattCharacteristic? = null
override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean {
val service = gatt.getService(MyServiceProfile.MY_SERVICE_UUID)
myCharacteristic =
service?.getCharacteristic(MyServiceProfile.MY_CHARACTERISTIC_UUID)
val myCharacteristicProperties = myCharacteristic?.properties ?: 0
return (myCharacteristicProperties and BluetoothGattCharacteristic.PROPERTY_READ != 0) &&
(myCharacteristicProperties and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0)
}
override fun initialize() {
setNotificationCallback(myCharacteristic).with { _, data ->
if (data.value != null) {
val value = String(data.value!!, Charsets.UTF_8)
defaultScope.launch {
myCharacteristicChangedChannel?.send(value)
}
}
}
beginAtomicRequestQueue()
.add(enableNotifications(myCharacteristic)
.fail { _: BluetoothDevice?, status: Int ->
log(Log.ERROR, "Could not subscribe: $status")
disconnect().enqueue()
}
)
.done {
log(Log.INFO, "Target initialized")
}
.enqueue()
}
override fun onServicesInvalidated() {
myCharacteristic = null
}
}
}
object MyServiceProfile {
val MY_SERVICE_UUID: UUID = UUID.fromString("8d67d51a-801b-43cb-aea2-bbec9d1211fd")
val MY_CHARACTERISTIC_UUID: UUID = UUID.fromString("8d67d51c-801b-43cb-aea2-bbec9d1211fd")
val RX_CHARACTERISTIC_UUID: UUID = UUID.fromString("8d67d51b-801b-43cb-aea2-bbec9d1211fd")
}
}
`
My task is to find a way (if there is one) to detect when an outgoing phone call is answered by the receiver.
After some research, I have tried the following:
A. BroadcastReceiver for PHONE_STATE action
<receiver android:name=".Interceptor">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE"/>
</intent-filter>
</receiver>
but outgoing calls never notifies back when phone call is answered.
class Interceptor: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
try {
when (intent.getStringExtra(TelephonyManager.EXTRA_STATE)) {
TelephonyManager.EXTRA_STATE_RINGING -> {
showToast(context, "ringing")
}
TelephonyManager.EXTRA_STATE_IDLE -> {
showToast(context, "idle")
}
TelephonyManager.EXTRA_STATE_OFFHOOK -> {
showToast(context, "off hook")
}
else -> {
showToast(context, intent.getStringExtra(TelephonyManager.EXTRA_STATE)!!)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun showToast(context: Context, s: String) {
Toast.makeText(context, s, Toast.LENGTH_SHORT).show()
}
}
i'm aware of READ_PRECISE_PHONE_STATE, but such permission is only for applications such as dialers, carrier applications, or ims applications.
B. TelecomManager + ConnectionService
#RequiresApi(Build.VERSION_CODES.M)
fun call(view: View) {
val telecomManager: TelecomManager = getSystemService(TELECOM_SERVICE) as TelecomManager
when {
ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED -> {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CALL_PHONE), 2333)
}
ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED -> {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_PHONE_STATE), 2334)
}
else -> {
try {
val phoneAccountHandle = PhoneAccountHandle(ComponentName(applicationContext, MyConnectionService::class.java), "dld")
val build = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
PhoneAccount
.builder(phoneAccountHandle, "1234") // phone account named 1234
.setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER or PhoneAccount.CAPABILITY_CALL_PROVIDER)
.build()
} else {
TODO("VERSION.SDK_INT < O")
}
telecomManager.registerPhoneAccount(build)
val parse = Uri.fromParts(PhoneAccount.SCHEME_TEL, PHONENUMBERTOCALL, null) // PHONENUMBERTOCALL == "1112223334", a valid telephone number
val extras = Bundle()
extras.putParcelable(EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle)
telecomManager.placeCall(parse, extras)
} catch (e: SecurityException) {
e.printStackTrace()
}
}
}
}
#RequiresApi(Build.VERSION_CODES.M)
class MyConnectionService : ConnectionService() {
private val TAG = this#MyConnectionService::javaClass.name
override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate: ")
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Log.d(TAG, "onStartCommand: ")
return super.onStartCommand(intent, flags, startId)
}
override fun onCreateIncomingConnection(connectionManagerPhoneAccount: PhoneAccountHandle, request: ConnectionRequest): Connection {
Log.d(TAG, "onCreateIncomingConnection: ")
return super.onCreateIncomingConnection(connectionManagerPhoneAccount, request)
}
override fun onCreateIncomingConnectionFailed(connectionManagerPhoneAccount: PhoneAccountHandle, request: ConnectionRequest) {
Log.d(TAG, "onCreateIncomingConnectionFailed: ")
super.onCreateIncomingConnectionFailed(connectionManagerPhoneAccount, request)
}
override fun onCreateOutgoingConnectionFailed(connectionManagerPhoneAccount: PhoneAccountHandle, request: ConnectionRequest) {
Log.d(TAG, "onCreateOutgoingConnectionFailed: ")
super.onCreateOutgoingConnectionFailed(connectionManagerPhoneAccount, request)
}
#RequiresApi(Build.VERSION_CODES.N_MR1)
override fun onCreateOutgoingConnection(connectionManagerPhoneAccount: PhoneAccountHandle, request: ConnectionRequest): Connection {
Log.d(TAG, "onCreateOutgoingConnection: ")
val connection = MyConnection(false)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
connection.connectionProperties = Connection.PROPERTY_SELF_MANAGED
}
connection.setAddress(
request.address,
TelecomManager.PRESENTATION_ALLOWED)
connection.extras = request.extras
connection.setInitialized()
return connection
}
override fun onCreateOutgoingHandoverConnection(fromPhoneAccountHandle: PhoneAccountHandle, request: ConnectionRequest): Connection {
Log.d(TAG, "onCreateOutgoingHandoverConnection: ")
return super.onCreateOutgoingHandoverConnection(fromPhoneAccountHandle, request)
}
override fun onCreateIncomingHandoverConnection(fromPhoneAccountHandle: PhoneAccountHandle, request: ConnectionRequest): Connection {
Log.d(TAG, "onCreateIncomingHandoverConnection: ")
return super.onCreateIncomingHandoverConnection(fromPhoneAccountHandle, request)
}
override fun onHandoverFailed(request: ConnectionRequest, error: Int) {
super.onHandoverFailed(request, error)
Log.d(TAG, "onHandoverFailed: ")
}
override fun onConference(connection1: Connection, connection2: Connection) {
super.onConference(connection1, connection2)
Log.d(TAG, "onConference: ")
}
override fun onRemoteConferenceAdded(conference: RemoteConference) {
super.onRemoteConferenceAdded(conference)
Log.d(TAG, "onRemoteConferenceAdded: ")
}
override fun onRemoteExistingConnectionAdded(connection: RemoteConnection) {
super.onRemoteExistingConnectionAdded(connection)
Log.d(TAG, "onRemoteExistingConnectionAdded: ")
}
override fun onConnectionServiceFocusLost() {
super.onConnectionServiceFocusLost()
Log.d(TAG, "onConnectionServiceFocusLost: ")
}
override fun onConnectionServiceFocusGained() {
super.onConnectionServiceFocusGained()
Log.d(TAG, "onConnectionServiceFocusGained: ")
}
}
#RequiresApi(Build.VERSION_CODES.M)
class MyConnection(isIncoming: Boolean): Connection() {
private val TAG = this#MyConnection::javaClass.name
init {
// Assume all calls are video capable.
// Assume all calls are video capable.
}
override fun onStateChanged(state: Int) {
super.onStateChanged(state)
Log.d(TAG, "onStateChanged: $state")
}
override fun onAnswer() {
super.onAnswer()
this.setActive()
Log.d(TAG, "onAnswer: ")
}
override fun onDisconnect() {
super.onDisconnect()
this.destroy()
Log.d(TAG, "onDisconnect: ")
}
}
but none of those callbacks triggers
C. (alternate solution) Reading call logs
private fun showCallLogs() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALL_LOG) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_CALL_LOG), PERMISSION_REQUEST_READ_LOGS)
} else {
val sb = StringBuffer()
val cursor = managedQuery(CallLog.Calls.CONTENT_URI, null, null, null, null)
// columns
val number = cursor.getColumnIndex(CallLog.Calls.NUMBER)
val duration = cursor.getColumnIndex(CallLog.Calls.DURATION)
val date = cursor.getColumnIndex(CallLog.Calls.DATE)
val type = cursor.getColumnIndex(CallLog.Calls.TYPE)
sb.append("Call Log : ")
while (cursor.moveToNext()) {
val phoneNumber = cursor.getString(number)
if (phoneNumber == PHONENUMBERTOCALL) {
// -- duration
val callDate = cursor.getString(date)
val callDayTime = Date(callDate.toLong())
// -- duration
if (callDayTime.month in 2..4) {
// -- duration
val callDuration = cursor.getString(duration)
// -- duration
// -- call type
val callType = cursor.getString(type)
val dirCode = callType.toInt()
var dir = ""
when (dirCode) {
CallLog.Calls.OUTGOING_TYPE -> dir = "OUTGOING"
CallLog.Calls.INCOMING_TYPE -> dir = "INCOMING"
CallLog.Calls.MISSED_TYPE -> dir = "MISSED"
}
// -- call type
sb.append("\n call report: \n date: $callDayTime \n call type: $dir \n duration: $callDuration")
sb.append("\n --------------------------------------------------")
}
}
}
cursor.close()
Log.d("INFO", sb.toString())
}
}
this solution works, but there will be several problems when publishing the app in the PlayStore, so this will never see the light (unfortunatelly).
Any help is appreciated, thank you in advance.
I'm working on a small app with a BLE device. Sometimes the scan callback works fine and I can find the device but other times the scan callback doesn't work and I was wondering if there's something wrong with my code or if there's a problem with my permission check.
Here's my code:
override fun onCreate(savedInstanceState: Bundle?) {
if (!isBLESupported(this)) {
Toast.makeText(this, "This device doesn't support bluetooth", Toast.LENGTH_SHORT).show()
finish()
} else {
if (!mBtAdapter!!.isEnabled) {
val enableBluetoothIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
startActivityForResult(enableBluetoothIntent, REQUEST_ENABLE_BLUETOOTH)
}
}
adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, macList)
list_view.adapter = adapter
private val lightUUID = UUID.fromString("0000ffb0-0000-1000-8000-00805f9b34fb")
scan_button.setOnClickListener {
scanForDeviceWithFilter(lightUUID)
}
}
private fun scanForDeviceWithFilter(serviceUUID: UUID) {
val uuid = ParcelUuid(id)
val filter = ScanFilter.Builder().setServiceUuid(uuid).build()
val filters = listOf(filter)
val settings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
checkBTPermissions()
mBtAdapter!!.bluetoothLeScanner.startScan(filters, settings, scanDevicesCallback)
}
private val scanDevicesCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult?) {
result?.let {
if (!macList.contains(result.device.name.toString())) {
macList.add(result.device.name.toString())
adapter.notifyDataSetChanged()
}
Log.d(TAG, "device found:${result.device}")
}
}
override fun onScanFailed(errorCode: Int) {
Log.d(TAG, "Scan failed $errorCode")
}
}
private fun isBLESupported(context: Context): Boolean {
return BluetoothAdapter.getDefaultAdapter() != null && context.packageManager.hasSystemFeature(
PackageManager.FEATURE_BLUETOOTH_LE
)
}
private fun checkBTPermissions() {
val permissionCheck = checkSelfPermission("Manifest.permission.ACCESS_FINE_LOCATION")
if (permissionCheck != 0) {
requestPermissions(
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 1001
)
}
}
init {
mBtAdapter = BluetoothAdapter.getDefaultAdapter()
}
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
}
}
I am trying to get a sensor value over BLE. I can connect to the sensor and onCharacteristicRead is called, but calling BluetoothGattCharacteristc.value or .getStringValue(0) etc. always returns the sensor device name instead of the characteristic value.
I can access the sensor value with a BLE app on my phone so I know that the sensor is sending the correct value.
BluetoothGattCallback:
private val gattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
bluetoothGatt?.discoverServices()
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) {
for (service: BluetoothGattService in bluetoothGatt!!.services) {
Log.i(TAG, service.uuid.toString())
Log.i(TAG, service.characteristics.size.toString())
for (characteristic: BluetoothGattCharacteristic in service.characteristics) {
//Read
bluetoothGatt?.readCharacteristic(characteristic)
if(characteristic.uuid == CHARACTERISTIC_UUID){
//Notify
Log.i(TAG, characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID).uuid.toString())
bluetoothGatt?.setCharacteristicNotification(characteristic, true)
val descriptor = characteristic
.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID).apply {
value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
}
bluetoothGatt?.writeDescriptor(descriptor)
}
}
}
}
}
override fun onCharacteristicRead(
gatt: BluetoothGatt?,
characteristic: BluetoothGattCharacteristic?,
status: Int
) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.i(TAG, characteristic!!.getStringValue(0)) // Returns device name
Log.i(TAG, String(characteristic.value)) // Returns device name
} else {
Log.w(TAG, "GATT failure")
}
}
override fun onCharacteristicChanged(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?) {
Log.i(TAG, String(characteristic!!.value))
}
}
onCharacteristicRead is called, but incorrect values are returned. onCharacteristicChanged is never called even though the sensor values are constantly updating. What have I done wrong?
This change solved the problem:
override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) {
for (service: BluetoothGattService in bluetoothGatt!!.services) {
for (characteristic: BluetoothGattCharacteristic in service.characteristics) {
if(characteristic.uuid == CHARACTERISTIC_UUID){
//Read
bluetoothGatt?.readCharacteristic(characteristic)
//Notify
Log.i(TAG, characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID).uuid.toString())
bluetoothGatt?.setCharacteristicNotification(characteristic, true)
val descriptor = characteristic
.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID).apply {
value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
}
bluetoothGatt?.writeDescriptor(descriptor)
}
}
}
}
}