Thanks in advance for helping. I am trying to work with a Serial Bluetooth connection where I am connecting and communicating to the IOT device serially over Bluetooth. So the device has On/Off switch which can basically close the Bluetooth connection when turned off the device. So I am trying to achieve that when the device is turned ON the app should auto-detect and auto-connect with the device all the time. So far I am able to achieve that by the code below.
Unfortunately, until the following error is encountered. Once this error is encountered the app keeps closing the connection all the time after connection requested with IOT device.
Error Log:
2024-01-03 16:01:00.535 8401-8416 System com.mybluetooth.iot E Uncaught exception thrown by finalizer
2024-01-03 16:01:00.541 8401-8416 System com.mybluetooth.iot E java.io.IOException: socket not created
at android.net.LocalSocketImpl.shutdownInput(LocalSocketImpl.java:371)
at android.net.LocalSocket.shutdownInput(LocalSocket.java:238)
at android.bluetooth.BluetoothSocket.close(BluetoothSocket.java:780)
at android.bluetooth.BluetoothSocket.finalize(BluetoothSocket.java:371)
at java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.java:339)
at java.lang.Daemons$FinalizerDaemon.processReference(Daemons.java:324)
at java.lang.Daemons$FinalizerDaemon.runInternal(Daemons.java:300)
at java.lang.Daemons$Daemon.run(Daemons.java:145)
at java.lang.Thread.run(Thread.java:1012)
2024-01-03 16:01:00.541 8401-8416 System com.mybluetooth.iot E Uncaught exception thrown by finalizer
2024-01-03 16:01:00.541 8401-8416 System com.mybluetooth.iot E java.io.IOException: socket not created
at android.net.LocalSocketImpl.shutdownInput(LocalSocketImpl.java:371)
at android.net.LocalSocket.shutdownInput(LocalSocket.java:238)
at android.bluetooth.BluetoothSocket.close(BluetoothSocket.java:780)
at android.bluetooth.BluetoothSocket.finalize(BluetoothSocket.java:371)
at java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.java:339)
at java.lang.Daemons$FinalizerDaemon.processReference(Daemons.java:324)
at java.lang.Daemons$FinalizerDaemon.runInternal(Daemons.java:300)
at java.lang.Daemons$Daemon.run(Daemons.java:145)
at java.lang.Thread.run(Thread.java:1012)
2024-01-03 16:01:00.542 8401-8416 System com.mybluetooth.iot E Uncaught exception thrown by finalizer
2024-01-03 16:01:00.542 8401-8416 System com.mybluetooth.iot E java.io.IOException: socket not created
at android.net.LocalSocketImpl.shutdownInput(LocalSocketImpl.java:371)
at android.net.LocalSocket.shutdownInput(LocalSocket.java:238)
at android.bluetooth.BluetoothSocket.close(BluetoothSocket.java:780)
at android.bluetooth.BluetoothSocket.finalize(BluetoothSocket.java:371)
at java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.java:339)
at java.lang.Daemons$FinalizerDaemon.processReference(Daemons.java:324)
at java.lang.Daemons$FinalizerDaemon.runInternal(Daemons.java:300)
at java.lang.Daemons$Daemon.run(Daemons.java:145)
at java.lang.Thread.run(Thread.java:1012)
**Error log for making Re-connect attempt after turning ON the IOT device**
2024-01-03 17:21:33.547 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: This is ACL_DISCONNECTED
2024-01-03 17:21:33.572 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: This is ACL_DISCONNECTED
2024-01-03 17:21:33.582 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: This is ACL_DISCONNECTED
2024-01-03 17:21:33.591 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: This is ACL_DISCONNECTED
2024-01-03 17:21:33.600 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: This is ACL_DISCONNECTED
2024-01-03 17:21:33.606 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: This is ACL_DISCONNECTED
2024-01-03 17:21:33.613 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: This is ACL_DISCONNECTED
2024-01-03 17:21:33.626 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: This is ACL_DISCONNECTED
2024-01-03 17:21:33.634 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: This is ACL_DISCONNECTED
2024-01-03 17:21:33.643 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: This is ACL_DISCONNECTED
2024-01-03 17:21:33.655 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: This is ACL_DISCONNECTED
2024-01-03 17:21:34.273 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: This is ACL_CONNECTED
2024-01-03 17:21:34.282 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: This is ACL_CONNECTED
2024-01-03 17:21:34.282 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: This is ACL_CONNECTED
2024-01-03 17:21:34.282 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: This is ACL_CONNECTED
2024-01-03 17:21:34.282 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: This is ACL_CONNECTED
2024-01-03 17:21:34.283 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: This is ACL_CONNECTED
2024-01-03 17:21:34.284 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: This is ACL_CONNECTED
2024-01-03 17:21:34.284 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: This is ACL_CONNECTED
2024-01-03 17:21:34.284 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: This is ACL_CONNECTED
2024-01-03 17:21:34.285 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: This is ACL_CONNECTED
2024-01-03 17:21:34.285 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: This is ACL_CONNECTED
2024-01-03 17:21:34.540 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: Disconnected State
2024-01-03 17:21:34.560 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: This is ACL Event
2024-01-03 17:21:34.585 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: Disconnected State
2024-01-03 17:21:34.611 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: This is ACL Event
2024-01-03 17:21:34.612 18742-18742 BaseActivity com.mybluetooth.iot E Base Activity: Connected
2024-01-03 17:21:34.612 18742-18742 DashboardA...........Kt com.mybluetooth.iot E :: This is for re-connected
2024-01-03 17:21:34.636 18742-18777 BT IO com.mybluetooth.iot E readByteArrayStream: Connection Disconnected from finally
2024-01-03 17:21:34.636 18742-18777 BT IO com.mybluetooth.iot E All Connections closed
BluetoothServiceProviderIO.kt
class BluetoothServiceProviderIO(val bluetoothSocket: BluetoothSocket?) {
private val inputStream: InputStream? = try {
bluetoothSocket?.inputStream
} catch (e: IOException) {
Log.e("BT IO", "Could not Open Input Stream")
e.printStackTrace()
throw SocketStreamException("Couldn't open InputStream socket")
}
private val outputStream: OutputStream? = try {
bluetoothSocket?.outputStream
} catch (e: IOException) {
Log.e("BT IO", "Could not Open OutputStream")
e.printStackTrace()
throw SocketStreamException("Couldn't open OutputStream socket")
}
/**
* Send array of bytes to bluetooth output stream.
*
* @param bytes data to send
* @return true if success, false if there was error occurred or disconnected
*/
fun send(data: ModelSendData): Boolean {
if (bluetoothSocket?.isConnected != true) return false
return try {
outputStream?.write(data.data)
outputStream?.flush()
true
} catch (e: IOException) {
e.printStackTrace()
false
}
}
@ExperimentalCoroutinesApi
fun readByteArrayStream(
delayMillis: Long = 10,
bufferCapacity: Int = 32
): Flow<ByteArray> = channelFlow {
if (inputStream == null) {
Log.e("BT IO", "input Stream is null")
throw NullPointerException("inputStream is null. Perhaps bluetoothSocket is also null")
}
val buffer = ByteArray(bufferCapacity)
while (isActive) {
try {
if (inputStream.available() > 0) {
val numBytes = inputStream.read(buffer)
val readBytes = ByteArray(size = numBytes)
for (i in 0 until numBytes) {
readBytes[i] = buffer[i]
}
delay(delayMillis)
this.trySend(readBytes).isSuccess
}
} catch (e: IOException) {
Log.e("BT IO", "readByteArrayStream: Connection Disconnected from Catch")
closeConnections()
error("Couldn't read bytes from flow. Disconnected")
} finally {
if (bluetoothSocket?.isConnected != true) {
Log.e("BT IO", "readByteArrayStream: Connection Disconnected from finally")
closeConnections()
break
}
}
}
}.flowOn(Dispatchers.IO)
/**
* Close the streams and socket connection.
*/
fun closeConnections() {
try {
inputStream?.close()
outputStream?.close()
bluetoothSocket?.close()
Log.e("BT IO", "All Connections closed")
} catch (e: Exception) {
Log.e("BT IO", "Connections close failed ${e.printStackTrace()}")
}
}
}
BluetoothServiceProvider.kt
@SuppressLint("MissingPermission")
class BluetoothServiceProvider(private val context: Context) {
val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
@Volatile
var currentBluetoothSocket: BluetoothSocket? = null
private var bluetoothServiceProviderIO: BluetoothServiceProviderIO? = null
@SuppressLint("HardwareIds")
fun isBluetoothAvailable() =
!(bluetoothManager.adapter == null || TextUtils.isEmpty(bluetoothManager.adapter.address))
fun isBluetoothEnabled() = bluetoothManager.adapter.isEnabled
fun getIO(bluetoothSocket: BluetoothSocket): BluetoothServiceProviderIO {
if (bluetoothServiceProviderIO?.bluetoothSocket == bluetoothSocket) {
return bluetoothServiceProviderIO as BluetoothServiceProviderIO
}
currentBluetoothSocket = bluetoothSocket
bluetoothServiceProviderIO = BluetoothServiceProviderIO(bluetoothSocket)
return bluetoothServiceProviderIO as BluetoothServiceProviderIO
}
fun getIO(): BluetoothServiceProviderIO {
return getIO(currentBluetoothSocket!!)
}
@SuppressLint("MissingPermission")
fun enable() = bluetoothManager.adapter.isEnabled
fun disable() = !bluetoothManager.adapter.isEnabled
@SuppressLint("MissingPermission")
fun bondedDevices(): List<BluetoothDevice> = bluetoothManager.adapter.bondedDevices.toList()
fun startDiscovery() = bluetoothManager.adapter.startDiscovery()
fun isDiscovering() = bluetoothManager.adapter.isDiscovering
fun cancelDiscovery() = if (bluetoothManager.adapter.isDiscovering) {
bluetoothManager.adapter.cancelDiscovery()
true
} else {
false
}
@ExperimentalCoroutinesApi
fun discoverDevices(): Flow<BluetoothDeviceWrapper> = callbackFlow<BluetoothDeviceWrapper> {
val filter = IntentFilter(BluetoothDevice.ACTION_FOUND).apply {
addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED)
addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
}
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
BluetoothDevice.ACTION_FOUND -> {
val device =
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) as BluetoothDevice?
val rssi =
intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, MIN_VALUE).toInt()
device?.let { trySend(BluetoothDeviceWrapper(it, rssi)).isSuccess }
}
BluetoothAdapter.ACTION_DISCOVERY_STARTED -> {
}
BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> {
}
}
}
}
context.registerReceiver(receiver, filter)
awaitClose {
context.unregisterReceiver(receiver)
}
}.flowOn(Dispatchers.IO)
@ExperimentalCoroutinesApi
fun checkDiscoveryStatus(): Flow<String> = callbackFlow<String> {
val filter = IntentFilter(BluetoothDevice.ACTION_FOUND).apply {
addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED)
addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
}
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
BluetoothDevice.ACTION_FOUND -> {
}
BluetoothAdapter.ACTION_DISCOVERY_STARTED -> {
trySend(BluetoothAdapter.ACTION_DISCOVERY_STARTED)
}
BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> {
trySend(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
}
}
}
}
context.registerReceiver(receiver, filter)
awaitClose {
context.unregisterReceiver(receiver)
}
}.flowOn(Dispatchers.IO)
@ExperimentalCoroutinesApi
fun getDeviceBondState(): Flow<Int> = callbackFlow<Int> {
val filter = IntentFilter().apply {
addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
}
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action == BluetoothDevice.ACTION_BOND_STATE_CHANGED) {
val mDevice: BluetoothDevice =
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)!!
//3 cases:
//case1: bonded already
if (mDevice.bondState == BluetoothDevice.BOND_BONDED) {
Log.e("BluetoothServiceProvider", "BroadcastReceiver: BOND_BONDED.")
}
//case2: creating a bond
if (mDevice.bondState == BluetoothDevice.BOND_BONDING) {
Log.e("BluetoothServiceProvider", "BroadcastReceiver: BOND_BONDING.")
}
//case3: breaking a bond
if (mDevice.bondState == BluetoothDevice.BOND_NONE) {
Log.e("BluetoothServiceProvider", "BroadcastReceiver: BOND_NONE.")
}
trySend(mDevice.bondState).isSuccess
}
}
}
context.registerReceiver(receiver, filter)
awaitClose {
context.unregisterReceiver(receiver)
}
}.flowOn(Dispatchers.IO)
fun connectToDevice(bluetoothDevice: BluetoothDevice,
uuid: UUID,
secure: Boolean = true): Deferred<BluetoothSocket> =
CoroutineScope(Dispatchers.IO).async {
val bluetoothSocket =
if (secure) bluetoothDevice.createRfcommSocketToServiceRecord(uuid)
else bluetoothDevice.createInsecureRfcommSocketToServiceRecord(uuid)
bluetoothSocket.apply {
currentBluetoothSocket = this@apply
connect()
}
}
@Throws(java.lang.Exception::class)
fun createBond(btDevice: BluetoothDevice?): Boolean {
val btClass = Class.forName("android.bluetooth.BluetoothDevice")
val createBondMethod = btClass.getMethod("createBond")
return createBondMethod.invoke(btDevice) as Boolean
}
@ExperimentalCoroutinesApi
fun aclEvents(): Flow<AclEvent> = callbackFlow<AclEvent> {
val filter = IntentFilter().apply {
addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
addAction(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED)
}
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
intent?.let {
val device =
it.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) as? BluetoothDevice
trySend(AclEvent(it.action ?: "", device)).isSuccess
}
}
}
context.registerReceiver(receiver, filter)
awaitClose {
context.unregisterReceiver(receiver)
}
}.flowOn(Dispatchers.IO)
fun closeConnections() = bluetoothServiceProviderIO?.closeConnections()
}
BTConnect.kt
class BTConnect(private val bluetoothServiceProvider: BluetoothServiceProvider) {
private val uuid: UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
@SuppressLint("MissingPermission")
fun connect(device: BluetoothDevice) = flow {
var state: BtConnection? = null
try {
bluetoothServiceProvider.connectToDevice(device, uuid, true).await()
bluetoothServiceProvider.currentBluetoothSocket.apply {
state = if (bluetoothServiceProvider.currentBluetoothSocket!!.isConnected) {
BtConnection.BtConnectedState(socket = bluetoothServiceProvider.currentBluetoothSocket!!)
} else {
BtConnection.BtDisconnectedState
}
}
} catch (e: IOException) {
state = BtConnection.BtDisconnectedState
} catch (e: Exception) {
state = BtConnection.BtDisconnectedState
Log.e("BT Connect", "connect: Failed other than IO Exception $e")
}
emit(state)
}
}
BaseActivity.kt
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
class BaseActivity : AppCompatActivity() {
var connectionState = MutableLiveData<BtConnection>()
private var progressDialog: CustomDialog? = null
lateinit var connectionStatus: Job
@Inject
lateinit var btConnect: BTConnect
@Inject
lateinit var bluetoothServiceProvider: BluetoothServiceProvider
@Inject
lateinit var preferenceHelper: PreferenceHelper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
setContentView(layoutResId)
window.insetsController?.hide(WindowInsets.Type.statusBars())
MyApplication.component.inject(this)
}
@SuppressLint("MissingPermission")
override fun onResume() {
isScreenVisible = true
super.onResume()
}
@SuppressLint("MissingPermission", "SetTextI18n")
open fun checkForBondedDevices(): BluetoothDevice? {
var device: BluetoothDevice? = null
if (bluetoothServiceProvider.bondedDevices().isEmpty()) {
device = null
} else {
for (i in bluetoothServiceProvider.bondedDevices()) {
if ((i.name == preferenceHelper.deviceName) && (i.address == preferenceHelper.deviceAddress)) {
device = i
}
}
}
return device
}
open fun checkSendStatus(device: BluetoothDevice) {
lifecycleScope.launch {
if (isDeviceConnected) isDeviceConnected = false
withContext(Dispatchers.Main) {
while (!isDeviceConnected && isScreenVisible) {
btConnect.connect(device).safeCollect {
delay(200)
if (it != null) {
checkBtConnectionState(it)
}
return@safeCollect
}
}
}
}
}
@SuppressLint("MissingPermission")
open suspend fun checkBtConnectionState(btConnection: BtConnection) {
connectionState.postValue(btConnection)
when (btConnection) {
is BtConnection.BtConnectingLoadingState -> {
isDeviceConnected = false
showLog("Connecting Loading State")
}
is BtConnection.BtConnectedState -> {
isDeviceConnected = true
showLog("Connected")
if (!firstTimeNotConnectCheck) {
onDeviceConnected()
} else {
onDeviceReconnected()
}
startCommunicationWithDevice()
}
is BtConnection.BtErrorConnectingState -> {
isDeviceConnected = false
showLog("Connecting State")
}
is BtConnection.BtDisconnectedState -> {
isDeviceConnected = false
showLog("Disconnected State")
onDeviceDisconnected()
}
}
}
open fun checkConnectionStateOfPairedDevice(device: BluetoothDevice) {
connectionStatus = lifecycleScope.launch {
withContext(Dispatchers.Main) {
bluetoothServiceProvider.aclEvents().safeCollect {
when (it.action) {
BluetoothDevice.ACTION_ACL_CONNECTED -> {
isDeviceConnected = true
}
BluetoothDevice.ACTION_ACL_DISCONNECTED -> {
isDeviceConnected = false
checkSendStatus(checkForBondedDevices()!!)
}
}
}
}
}
}
@SuppressLint("MissingPermission")
open suspend fun startCommunicationWithDevice() {
bluetoothServiceProvider.readByteArrayStream().safeCollect {
if (it.isNotEmpty()) {
processReceivedBytesForResponse(it)
} else {
showLog("Data not received")
}
}
}
open fun processReceivedBytesForResponse(byteArray: ByteArray) {
try {
if (byteArray.isNotEmpty()) {
// Process data
}
} catch (e: Exception) {
showLog("From Processed Received Data: $e")
}
}
override fun onPause() {
bluetoothServiceProvider.closeConnections()
isScreenVisible = false
super.onPause()
}
private fun showLog(s: String) {
Log.e(tag, "Base Activity: $s")
}
open fun showToast(msg: String) {
if (msg.isNotEmpty()) Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
}
open fun showProgressDialog() {
progressDialog = CustomDialog(this)
progressDialog!!.setMessage("Please wait...")
progressDialog!!.setProgressStyle(ProgressDialog.STYLE_SPINNER)
progressDialog!!.isIndeterminate = true
progressDialog!!.setCancelable(false)
progressDialog!!.show()
}
open fun hideProgressDialog() {
if (progressDialog != null) {
progressDialog!!.dismiss()
}
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus) {
hideSystemUI()
}
}
abstract fun onDeviceConnected()
abstract fun onDeviceReconnected()
abstract fun onDeviceDisconnected()
}
This is complete implementation for establishing and re-establishing the connection after device is restarted/switch ON.
Appreciate for your time. Thank you.
This is one my refernce code for developing this codebase. https://github.com/ThanosFisherman/BlueFlow