diff --git a/android/app/src/main/kotlin/me/kavishdevar/librepods/database/apple/AppleDao.kt b/android/app/src/main/kotlin/me/kavishdevar/librepods/database/apple/AppleDao.kt index 5af4510c8..d9cb56b02 100644 --- a/android/app/src/main/kotlin/me/kavishdevar/librepods/database/apple/AppleDao.kt +++ b/android/app/src/main/kotlin/me/kavishdevar/librepods/database/apple/AppleDao.kt @@ -3,6 +3,7 @@ package me.kavishdevar.librepods.database.apple import android.util.Log import androidx.room3.Dao import androidx.room3.Query +import androidx.room3.Transaction import androidx.room3.Upsert import me.kavishdevar.librepods.bluetooth.MacAddress import me.kavishdevar.librepods.data.apple.AppleCache @@ -23,18 +24,23 @@ interface AppleDao { @Upsert suspend fun upsert(device: AppleEntity) + // read-modify-write: without a transaction, concurrent saves (cache is saved on every state change) + // overwrite each other with stale columns, which is how metadata ended up empty in the db + @Transaction suspend fun updateSettings(macAddress: MacAddress, settings: AppleSettings) { Log.d(TAG, "Updating settings for $macAddress: $settings") val device = get(macAddress)?: AppleEntity(macAddress = macAddress, settings = settings, metadata = AppleMetadata(), cache = AppleCache()) upsert(device.copy(settings = settings)) } + @Transaction suspend fun updateMetadata(macAddress: MacAddress, metadata: AppleMetadata) { Log.d(TAG, "Updating metadata for $macAddress: $metadata") val device = get(macAddress)?: AppleEntity(macAddress = macAddress, settings = AppleSettings(), metadata = metadata, cache = AppleCache()) upsert(device.copy(metadata = metadata)) } + @Transaction suspend fun saveCache(macAddress: MacAddress, cache: AppleCache) { Log.d(TAG, "Saving cache for $macAddress: $cache") val device = get(macAddress)?: AppleEntity(macAddress = macAddress, settings = AppleSettings(), metadata = AppleMetadata(), cache = cache) diff --git a/android/app/src/main/kotlin/me/kavishdevar/librepods/devices/AppleDevice.kt b/android/app/src/main/kotlin/me/kavishdevar/librepods/devices/AppleDevice.kt index 595a791b9..6192e28a3 100644 --- a/android/app/src/main/kotlin/me/kavishdevar/librepods/devices/AppleDevice.kt +++ b/android/app/src/main/kotlin/me/kavishdevar/librepods/devices/AppleDevice.kt @@ -261,9 +261,16 @@ class AppleDevice( } } + // null until the AirPods send their information packet (or it is restored from the db) + private fun firmwareMajorVersion(): Int? = metadata.value.version3.firstOrNull()?.digitToIntOrNull() + fun startHeadTracking() { + val firmwareMajor = firmwareMajorVersion() ?: run { + Log.w(TAG, "firmware version unknown, not starting head tracking") + return + } aacp.setSensorServiceReportInterval( - sensorServiceType = if (metadata.value.version3.first().digitToInt() >= 8) SensorServiceType.DEVMOTION6 else SensorServiceType.ACTIVITY, + sensorServiceType = if (firmwareMajor >= 8) SensorServiceType.DEVMOTION6 else SensorServiceType.ACTIVITY, interval = _settings.value.headTrackingInterval ) _state.update { @@ -274,10 +281,13 @@ class AppleDevice( } fun stopHeadTracking() { - aacp.setSensorServiceReportInterval( - sensorServiceType = if (metadata.value.version3.first().digitToInt() >= 8) SensorServiceType.DEVMOTION6 else SensorServiceType.ACTIVITY, - interval = Duration.ZERO - ) + val firmwareMajor = firmwareMajorVersion() + if (firmwareMajor != null) { + aacp.setSensorServiceReportInterval( + sensorServiceType = if (firmwareMajor >= 8) SensorServiceType.DEVMOTION6 else SensorServiceType.ACTIVITY, + interval = Duration.ZERO + ) + } _state.update { it.copy( headTrackingState = BuddyState.INACTIVE, @@ -338,8 +348,12 @@ class AppleDevice( // AACPManager sets hrmActive true when a valid reading is received fun startHr(): Boolean { + val firmwareMajor = firmwareMajorVersion() ?: run { + Log.w(TAG, "firmware version unknown, not starting heart rate monitoring") + return false + } val success = aacp.setSensorServiceReportInterval( - sensorServiceType = if (metadata.value.version3.first().digitToInt() >= 9) SensorServiceType.HEARTRATE_COMMAND else SensorServiceType.HEARTRATE, + sensorServiceType = if (firmwareMajor >= 9) SensorServiceType.HEARTRATE_COMMAND else SensorServiceType.HEARTRATE, interval = 1.seconds ) if (success) { @@ -352,8 +366,12 @@ class AppleDevice( return success } fun stopHr(): Boolean { + val firmwareMajor = firmwareMajorVersion() ?: run { + Log.w(TAG, "firmware version unknown, can't stop heart rate monitoring") + return false + } val success = aacp.setSensorServiceReportInterval( - sensorServiceType = if (metadata.value.version3.first().digitToInt() >= 9) SensorServiceType.HEARTRATE_COMMAND else SensorServiceType.HEARTRATE, + sensorServiceType = if (firmwareMajor >= 9) SensorServiceType.HEARTRATE_COMMAND else SensorServiceType.HEARTRATE, interval = Duration.ZERO ) if (state.value.hrmState != BuddyState.INACTIVE && success) _state.update { diff --git a/android/app/src/main/kotlin/me/kavishdevar/librepods/services/LibrePodsService.kt b/android/app/src/main/kotlin/me/kavishdevar/librepods/services/LibrePodsService.kt index df87fe626..92b3fb55d 100644 --- a/android/app/src/main/kotlin/me/kavishdevar/librepods/services/LibrePodsService.kt +++ b/android/app/src/main/kotlin/me/kavishdevar/librepods/services/LibrePodsService.kt @@ -54,6 +54,7 @@ import me.kavishdevar.librepods.bluetooth.MacAddress import me.kavishdevar.librepods.bluetooth.aacp.types.ControlCommandIdentifier import me.kavishdevar.librepods.bluetooth.aacp.types.MagicKeyType import me.kavishdevar.librepods.bluetooth.verifyRPA +import me.kavishdevar.librepods.data.apple.BuddyState import me.kavishdevar.librepods.data.heartrate.HeartRateSample import me.kavishdevar.librepods.data.recording.Recording import me.kavishdevar.librepods.database.app.AppSettingsEntity @@ -876,6 +877,13 @@ class LibrePodsService: Service() { appleRepository.saveMetadata(device.macAddress, metadata) + // startHr() needs the firmware version; on connect it may not have been known yet + if (previousMetadata.version3.isEmpty() && metadata.version3.isNotEmpty() && + device.settings.value.hrmAlertEnabled && device.state.value.hrmState == BuddyState.INACTIVE + ) { + device.startHr() + } + setAppleBluetoothMetadata(device) val notificationManager = getSystemService(NotificationManager::class.java)