I am trying to add bluetooth cast functionality for player app.
I am connecting to paired bluetooth speaker. Bluetooth manager I get like this :
private val bluetoothManager by lazy {
requireContext.getSystemService(BluetoothManager::class.java)
}
private val bluetoothAdapter by lazy {
bluetoothManager?.adapter
}
My connecting method :
pairedDevicesAdapter.setOnItemClickListener { deviceToConnect ->
device = deviceToConnect
bluetoothAdapter?.getProfileProxy(
requireContext,
object : BluetoothProfile.ServiceListener{
override fun onServiceConnected(profile: Int, proxy: BluetoothProfile?) {
a2dp = proxy as BluetoothA2dp
try{
a2dp.javaClass
.getMethod("connect", BluetoothDevice::class.java)
.invoke(a2dp, device)
} catch (e : Exception) {
Log.d("CHECKTAGS", e.stackTraceToString())
}
}
override fun onServiceDisconnected(profile: Int) {
device?.let {
disConnectUsingBluetoothA2dp(it)
}
}
}, BluetoothProfile.A2DP
)
}
And disconnecting method :
private fun disConnectUsingBluetoothA2dp(deviceToConnect: BluetoothDevice){
try {
a2dp.javaClass.getMethod(
"disconnect",
BluetoothDevice::class.java
).invoke(a2dp, deviceToConnect)
bluetoothAdapter?.closeProfileProxy(BluetoothProfile.A2DP, a2dp)
} catch (e: Exception) {
e.printStackTrace()
}
}
It connects fine. But disconnection works partially. In bluetooth settings it says that the speaker is still connected for calls.
Problem is if I turn the speaker off and on, it automatically connecting for audio, and my player starts to translate audio on speaker again.
I tried other disconnecting implementation methods I found in old posts, but what ever I do device keeps its connection for calls.
Update
Guess problem is not that connection to calls remains. Its just that paired device reconnects itself to the phone (and automatocally used for audio again) once its rebooted. Even if it seems to be completely disconnected with app's disconnect function (actually same if it is disconnected in phone's settings)
I did not know about this bluetooth behavior. Is there a way to handle it? What I am looking for: if my app is running, and some device is trying to auto connect, I catch request for connection and prevent it. But I see only ACTION_ACL_DISCONNECT_REQUESTED event.
Kind of managed it with ACTION_ACL_CONNECTED. Receiver returns connecting device:
class ConnectingDeviceReceiver(
private val connectingDevice: (BluetoothDevice) -> Unit
): BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when(intent?.action) {
BluetoothDevice.ACTION_ACL_CONNECTED -> {
val device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(
BluetoothDevice.EXTRA_DEVICE,
BluetoothDevice::class.java
)
} else {
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
}
device?.let(connectingDevice)
}
}
}
}
Then I initialize receiver:
@SuppressLint("MissingPermission")
private val connectingDeviceReceiver = ConnectingDeviceReceiver { deviceToDisconnect ->
disconnect(deviceToDisconnect)
}
val filter = IntentFilter()
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
context.registerReceiver(
connectingDeviceReceiver,
filter
)
And ofcource if I call disconnect function right after receiver is getting triggered, it won't do anything, it needs to find a right moment for that. So I need to keep firing disconnect in order for it to work.
And I can't find any way to detect, when device is no longer connecting for a2dp... If it is always false, there is no event to listen to when disconnect function succeeds. And since device keeps its connection for calls, I can't see it to be disconnected either.
@SuppressLint("MissingPermission")
private fun disconnect(device: BluetoothDevice) {
val serviceListener: BluetoothProfile.ServiceListener = object :
BluetoothProfile.ServiceListener {
override fun onServiceDisconnected(profile: Int) {}
@SuppressLint("DiscouragedPrivateApi")
override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
val disconnect = BluetoothA2dp::class.java.getDeclaredMethod(
"disconnect",
BluetoothDevice::class.java
)
disconnect.isAccessible = true
disconnect.invoke(proxy, device)
bluetoothAdapter?.closeProfileProxy(profile, proxy)
}
}
lifecycleScope.launch {
while (true){
delay(50)
bluetoothAdapter?.getProfileProxy(context, serviceListener, BluetoothProfile.A2DP)
}
}
}