From 5eb13ace0cc4082e657d22e105ebaccd3053a486 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Tue, 20 May 2025 09:54:18 +0530 Subject: [PATCH] android: improve ble-based autoconnection --- .../librepods/services/AirPodsService.kt | 64 ++++++++++++------- .../kavishdevar/librepods/utils/BLEManager.kt | 16 +++-- .../librepods/utils/MediaController.kt | 13 ++-- 3 files changed, 60 insertions(+), 33 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 7520c3e..09ddeba 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 @@ -213,6 +213,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList this@AirPodsService, getSharedPreferences("settings", MODE_PRIVATE).getString("name", "AirPods Pro") ?: "AirPods" ) + if (isConnectedLocally) return val leftLevel = bleManager.getMostRecentStatus()?.leftBattery?: 0 val rightLevel = bleManager.getMostRecentStatus()?.rightBattery?: 0 val caseLevel = bleManager.getMostRecentStatus()?.caseBattery?: 0 @@ -243,24 +244,23 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList } override fun onBatteryChanged(device: BLEManager.AirPodsStatus) { - // if (!isConnectedLocally) { - // val leftLevel = bleManager.getMostRecentStatus()?.leftBattery?: 0 - // val rightLevel = bleManager.getMostRecentStatus()?.rightBattery?: 0 - // val caseLevel = bleManager.getMostRecentStatus()?.caseBattery?: 0 - // val leftCharging = bleManager.getMostRecentStatus()?.isLeftCharging - // val rightCharging = bleManager.getMostRecentStatus()?.isRightCharging - // val caseCharging = bleManager.getMostRecentStatus()?.isCaseCharging + if (isConnectedLocally) return + val leftLevel = bleManager.getMostRecentStatus()?.leftBattery?: 0 + val rightLevel = bleManager.getMostRecentStatus()?.rightBattery?: 0 + val caseLevel = bleManager.getMostRecentStatus()?.caseBattery?: 0 + val leftCharging = bleManager.getMostRecentStatus()?.isLeftCharging + val rightCharging = bleManager.getMostRecentStatus()?.isRightCharging + val caseCharging = bleManager.getMostRecentStatus()?.isCaseCharging - // batteryNotification.setBatteryDirect( - // leftLevel = leftLevel, - // leftCharging = leftCharging == true, - // rightLevel = rightLevel, - // rightCharging = rightCharging == true, - // caseLevel = caseLevel, - // caseCharging = caseCharging == true - // ) - // updateBattery() - // } + batteryNotification.setBatteryDirect( + leftLevel = leftLevel, + leftCharging = leftCharging == true, + rightLevel = rightLevel, + rightCharging = rightCharging == true, + caseLevel = caseLevel, + caseCharging = caseCharging == true + ) + updateBattery() Log.d("AirPodsBLEService", "Battery changed") } @@ -413,6 +413,9 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList connectAudio(this@AirPodsService, device) justEnabledA2dp = true registerA2dpConnectionReceiver() + if (MediaController.getMusicActive()) { + MediaController.userPlayedTheMedia = true + } } else if (newInEarData == listOf(false, false)) { MediaController.sendPause(force = true) if (config.disconnectWhenNotWearing) { @@ -434,7 +437,6 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList if (newInEarData.sorted() != inEarData.sorted()) { inEarData = newInEarData - if (inEar == true) { if (!justEnabledA2dp) { justEnabledA2dp = false @@ -1674,8 +1676,19 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList @RequiresApi(Build.VERSION_CODES.R) @SuppressLint("MissingPermission") fun takeOver(takingOverFor: String) { - if (isConnectedLocally || !CrossDevice.isAvailable || bleManager.getMostRecentStatus()?.isLeftInEar == true || bleManager.getMostRecentStatus()?.isRightInEar == true) { - Log.d("AirPodsService", "Already connected or not available for takeover") + if (isConnectedLocally) { + Log.d("AirPodsService", "Already connected locally, skipping") + return + } + + if (CrossDevice.isAvailable) { + Log.d("AirPodsService", "CrossDevice is available, continuing") + } + else if (bleManager.getMostRecentStatus()?.isLeftInEar == true || bleManager.getMostRecentStatus()?.isRightInEar == true) { + Log.d("AirPodsService", "At least one AirPod is in ear, continuing") + } + else { + Log.d("AirPodsService", "CrossDevice not available and AirPods not in ear, skipping") return } @@ -1684,6 +1697,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList "call" -> config.takeoverWhenRingingCall else -> false } + if (!shouldTakeOverPState) { Log.d("AirPodsService", "Not taking over audio, phone state takeover disabled") return @@ -1704,6 +1718,12 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList return } + if (takingOverFor == "music") { + Log.d("AirPodsService", "Pausing music so that it doesn't play through speakers") + MediaController.pausedForCrossDevice = true + MediaController.sendPause(true) + } + Log.d("AirPodsService", "Taking over audio") CrossDevice.sendRemotePacket(CrossDevicePackets.REQUEST_DISCONNECT.packet) Log.d("AirPodsService", macAddress) @@ -1810,7 +1830,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList aacpManager.sendSetFeatureFlagsPacket() aacpManager.sendNotificationRequest() Log.d("AirPodsService", "Requesting proximity keys") - aacpManager.sendRequestProximityKeys(AACPManager.Companion.ProximityKeyType.IRK.value) + aacpManager.sendRequestProximityKeys((AACPManager.Companion.ProximityKeyType.IRK.value + AACPManager.Companion.ProximityKeyType.ENC_KEY.value).toByte()) CoroutineScope(Dispatchers.IO).launch { aacpManager.sendPacket(aacpManager.createHandshakePacket()) delay(200) @@ -1818,7 +1838,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList delay(200) aacpManager.sendNotificationRequest() delay(200) - aacpManager.sendRequestProximityKeys(AACPManager.Companion.ProximityKeyType.IRK.value) + aacpManager.sendRequestProximityKeys((AACPManager.Companion.ProximityKeyType.IRK.value+AACPManager.Companion.ProximityKeyType.ENC_KEY.value).toByte()) startHeadTracking() Handler(Looper.getMainLooper()).postDelayed({ aacpManager.sendPacket(aacpManager.createHandshakePacket()) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/BLEManager.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/BLEManager.kt index 0e3f9ab..3c5c702 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/BLEManager.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/BLEManager.kt @@ -336,14 +336,16 @@ class BLEManager(private val context: Context) { val isLeftInEar = if (xorFactor) (status and 0x08) != 0 else (status and 0x02) != 0 val isRightInEar = if (xorFactor) (status and 0x02) != 0 else (status and 0x08) != 0 - val leftBatteryNibble = if (xorFactor) (podsBattery shr 4) and 0x0F else podsBattery and 0x0F - val rightBatteryNibble = if (xorFactor) podsBattery and 0x0F else (podsBattery shr 4) and 0x0F + val isFlipped = !primaryLeft + + val leftBatteryNibble = if (isFlipped) (podsBattery shr 4) and 0x0F else podsBattery and 0x0F + val rightBatteryNibble = if (isFlipped) podsBattery and 0x0F else (podsBattery shr 4) and 0x0F + + val caseBattery = flagsCase and 0x0F + val flags = (flagsCase shr 4) and 0x0F - val caseBattery = (flagsCase shr 4) and 0x0F - val flags = flagsCase and 0x0F - - val isRightCharging = if (xorFactor) (flags and 0x02) != 0 else (flags and 0x01) != 0 - val isLeftCharging = if (xorFactor) (flags and 0x01) != 0 else (flags and 0x02) != 0 + val isLeftCharging = if (isFlipped) (flags and 0x02) != 0 else (flags and 0x01) != 0 + val isRightCharging = if (isFlipped) (flags and 0x01) != 0 else (flags and 0x02) != 0 val isCaseCharging = (flags and 0x04) != 0 val lidOpen = ((lid shr 3) and 0x01) == 0 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 edee981..c0c8f00 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 @@ -88,11 +88,8 @@ object MediaController { userPlayedTheMedia = audioManager.isMusicActive }, 7) // i have no idea why android sends an event a hundred times after the user does something. } - Log.d("MediaController", "pausedforcrossdevice: $pausedForCrossDevice Ear detection status: ${ServiceManager.getService()?.earDetectionNotification?.status}, music active: ${audioManager.isMusicActive} and cross device available: ${CrossDevice.isAvailable}") + Log.d("MediaController", "pausedforcrossdevice: $pausedForCrossDevice") if (!pausedForCrossDevice && audioManager.isMusicActive) { - Log.d("MediaController", "Pausing for cross device and taking over.") - sendPause(true) - pausedForCrossDevice = true ServiceManager.getService()?.takeOver("music") } } @@ -143,6 +140,14 @@ object MediaController { ) ) } + if (!audioManager.isMusicActive) { + Log.d("MediaController", "Setting iPausedTheMedia to false") + iPausedTheMedia = false + } + if (pausedForCrossDevice) { + Log.d("MediaController", "Setting pausedForCrossDevice to false") + pausedForCrossDevice = false + } } @Synchronized