mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-15 10:46:12 -04:00
fix: Eliminate Float? boxing from battery display and cache merge
Two SIGSEGV native crashes recurred on Android 10 in 5.1.4-rc0 inside JIT-cached code at toBatteryFloat+4 (popup) and mergeBatterySlot+40 (cache merge). Both functions had a boxed Float? unbox at function entry that R8 horizontally merged into stdlib host classes, where Android 10 ART JIT miscompiled the unbox. Convert PodDevice battery getters to non-null Float with BATTERY_UNKNOWN sentinel and propagate primitive Float through every display/persistence consumer. mergeBatterySlot now takes primitive Float; toBatteryFloat and toBatteryOrNull are deleted. Add isKnownBattery and batteryProgress helpers used everywhere instead of scattered nullable checks. Raw live extraction in toCachedState avoids touching the unified getter so cached values aren't refreshed as live.
This commit is contained in:
@@ -3,6 +3,7 @@ package eu.darken.capod.monitor.core
|
||||
import eu.darken.capod.monitor.core.cache.CachedDeviceState
|
||||
import eu.darken.capod.pods.core.apple.PodModel
|
||||
import eu.darken.capod.pods.core.apple.aap.AapPodState
|
||||
import eu.darken.capod.pods.core.apple.ble.BATTERY_UNKNOWN
|
||||
import eu.darken.capod.pods.core.apple.ble.devices.DualApplePods
|
||||
import io.kotest.matchers.nulls.shouldBeNull
|
||||
import io.kotest.matchers.nulls.shouldNotBeNull
|
||||
@@ -57,7 +58,7 @@ class PodDeviceCacheTest : BaseTest() {
|
||||
device.batteryLeft shouldBe 0.8f
|
||||
device.batteryRight shouldBe 0.7f
|
||||
device.batteryCase shouldBe 0.5f
|
||||
device.batteryHeadset.shouldBeNull()
|
||||
device.batteryHeadset shouldBe BATTERY_UNKNOWN
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -252,6 +252,37 @@ class ToCachedStateTest : BaseTest() {
|
||||
result.rightEarbudSerial shouldBe "R-1"
|
||||
result.marketingVersion shouldBe "9999"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `DeviceInfo-only update preserves existing slot timestamps (no live refresh)`() {
|
||||
// Regression: if toCachedState consults the unified PodDevice.batteryX getter (which
|
||||
// falls back to cache), an AAP-only DeviceInfo update would re-stamp every slot with
|
||||
// `now`, refreshing stale cached readings indefinitely. Raw live extraction prevents
|
||||
// that.
|
||||
val oldStamp = now.minusSeconds(3600)
|
||||
val existing = CachedDeviceState(
|
||||
profileId = "test-profile",
|
||||
model = PodModel.AIRPODS_PRO3,
|
||||
left = CachedDeviceState.CachedBatterySlot(0.8f, oldStamp),
|
||||
right = CachedDeviceState.CachedBatterySlot(0.7f, oldStamp),
|
||||
case = CachedDeviceState.CachedBatterySlot(0.5f, oldStamp),
|
||||
marketingVersion = "OLD",
|
||||
lastSeenAt = oldStamp,
|
||||
)
|
||||
val device = PodDevice(
|
||||
profileId = "test-profile",
|
||||
ble = null,
|
||||
aap = AapPodState(deviceInfo = deviceInfo(marketingVersion = "NEW")),
|
||||
profileModel = PodModel.AIRPODS_PRO3,
|
||||
)
|
||||
|
||||
val result = device.toCachedState(existing, now).shouldNotBeNull()
|
||||
result.marketingVersion shouldBe "NEW"
|
||||
result.left?.updatedAt shouldBe oldStamp
|
||||
result.right?.updatedAt shouldBe oldStamp
|
||||
result.case?.updatedAt shouldBe oldStamp
|
||||
result.left?.percent shouldBe 0.8f
|
||||
}
|
||||
}
|
||||
|
||||
private fun deviceInfo(
|
||||
|
||||
Reference in New Issue
Block a user