From 364a6f4b64e40d7a8a7d792017bd779609eea524 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Sat, 10 May 2025 20:11:20 +0530 Subject: [PATCH] android: fix ear detection when none are in use and either or both are worn Music would start playing when neither are in ear, but even one is worn. This happens even when the music was not playing when they were removed (or, connected first) --- .../librepods/services/AirPodsService.kt | 57 +++++++++---------- .../librepods/utils/MediaController.kt | 7 +-- 2 files changed, 30 insertions(+), 34 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt index 7ea4ff4..f89f650 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt @@ -1468,39 +1468,36 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList ) { connectAudio(this@AirPodsService, device) justEnabledA2dp = true - val bluetoothAdapter = - this@AirPodsService.getSystemService( - BluetoothManager::class.java - ).adapter - bluetoothAdapter.getProfileProxy( - this@AirPodsService, - object : BluetoothProfile.ServiceListener { - override fun onServiceConnected( - profile: Int, - proxy: BluetoothProfile - ) { - if (profile == BluetoothProfile.A2DP) { - val connectedDevices = - proxy.connectedDevices - if (connectedDevices.isNotEmpty()) { - MediaController.sendPlay() - } + val a2dpConnectionStateReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED") { + val state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED) + val previousState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED) + val device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) + + Log.d("MediaController", "A2DP state changed: $previousState -> $state for device: ${device?.address}") + + if (state == BluetoothProfile.STATE_CONNECTED && + previousState != BluetoothProfile.STATE_CONNECTED && + device?.address == this@AirPodsService.device?.address) { + + Log.d("MediaController", "A2DP connected, sending play command") + MediaController.sendPlay() + MediaController.iPausedTheMedia = false + + context.unregisterReceiver(this) } - bluetoothAdapter.closeProfileProxy( - profile, - proxy - ) } - - override fun onServiceDisconnected( - profile: Int - ) { - } - }, - BluetoothProfile.A2DP - ) - + } + } + val a2dpIntentFilter = IntentFilter("android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + registerReceiver(a2dpConnectionStateReceiver, a2dpIntentFilter, RECEIVER_EXPORTED) + } else { + registerReceiver(a2dpConnectionStateReceiver, a2dpIntentFilter) + } } else if (newInEarData == listOf(false, false)) { + MediaController.sendPause(force = true) disconnectAudio(this@AirPodsService, device) } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/MediaController.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/MediaController.kt index ccc9ed4..38ab3af 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/MediaController.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/MediaController.kt @@ -82,7 +82,6 @@ object MediaController { if (configs != null && !iPausedTheMedia) { Log.d("MediaController", "Seems like the user changed the state of media themselves, now I won't play until the ear detection pauses it.") handler.postDelayed({ - iPausedTheMedia = !audioManager.isMusicActive userPlayedTheMedia = audioManager.isMusicActive }, 7) // i have no idea why android sends an event a hundred times after the user does something. } @@ -98,9 +97,9 @@ object MediaController { @Synchronized fun sendPause(force: Boolean = false) { - Log.d("MediaController", "Sending pause with iPausedTheMedia: $iPausedTheMedia, userPlayedTheMedia: $userPlayedTheMedia") - if ((audioManager.isMusicActive && !userPlayedTheMedia) || force) { - iPausedTheMedia = true + Log.d("MediaController", "Sending pause with iPausedTheMedia: $iPausedTheMedia, userPlayedTheMedia: $userPlayedTheMedia, isMusicActive: ${audioManager.isMusicActive}, force: $force") + if ((audioManager.isMusicActive) && (!userPlayedTheMedia || force)) { + iPausedTheMedia = if (force) audioManager.isMusicActive else true userPlayedTheMedia = false audioManager.dispatchMediaKeyEvent( KeyEvent(