refactor: Reorganize pods package into ble/ and aap/ domains

Move BLE-specific code (snapshots, devices, factories, protocol) under apple/ble/. Move AAP code under apple/aap/ with protocol/ subdirectory. PodModel stays in apple/ as the only shared type.
This commit is contained in:
darken
2026-03-31 19:17:09 +02:00
committed by Matthias Urhahn
parent 94e28fb0b5
commit a8593eecd3
149 changed files with 808 additions and 771 deletions
@@ -5,7 +5,7 @@ import eu.darken.capod.common.theming.ThemeColor
import eu.darken.capod.common.theming.ThemeMode
import eu.darken.capod.common.theming.ThemeStyle
import eu.darken.capod.main.core.MonitorMode
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.profiles.core.AppleDeviceProfile
import eu.darken.capod.profiles.core.DeviceProfilesContainer
import eu.darken.capod.reaction.core.autoconnect.AutoConnectCondition
@@ -8,7 +8,7 @@ import eu.darken.capod.common.theming.ThemeColor
import eu.darken.capod.common.theming.ThemeMode
import eu.darken.capod.common.theming.ThemeStyle
import eu.darken.capod.main.core.MonitorMode
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.profiles.core.AppleDeviceProfile
import eu.darken.capod.profiles.core.DeviceProfile
import eu.darken.capod.profiles.core.DeviceProfilesContainer
@@ -11,7 +11,7 @@ import eu.darken.capod.main.core.PermissionTool
import eu.darken.capod.monitor.core.DeviceMonitor
import eu.darken.capod.monitor.core.PodDevice
import eu.darken.capod.monitor.core.worker.MonitorControl
import eu.darken.capod.pods.core.BlePodSnapshot
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
import eu.darken.capod.profiles.core.AppleDeviceProfile
import eu.darken.capod.profiles.core.DeviceProfile
import eu.darken.capod.profiles.core.DeviceProfilesRepo
@@ -1,15 +1,16 @@
package eu.darken.capod.monitor.core
import eu.darken.capod.pods.core.BlePodSnapshot
import eu.darken.capod.pods.core.HasCase
import eu.darken.capod.pods.core.HasChargeDetectionDual
import eu.darken.capod.pods.core.HasEarDetection
import eu.darken.capod.pods.core.HasEarDetectionDual
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.DualApplePods
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
import eu.darken.capod.pods.core.apple.ble.devices.HasCase
import eu.darken.capod.pods.core.apple.ble.devices.HasChargeDetectionDual
import eu.darken.capod.pods.core.apple.ble.devices.HasEarDetection
import eu.darken.capod.pods.core.apple.ble.devices.HasEarDetectionDual
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.DualApplePods
import eu.darken.capod.pods.core.apple.protocol.aap.AapPodState
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting
import eu.darken.capod.pods.core.apple.aap.AapPodState
import eu.darken.capod.pods.core.apple.aap.protocol.AapSetting
import eu.darken.capod.pods.core.apple.ble.devices.ApplePods
import io.kotest.matchers.nulls.shouldBeNull
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe
@@ -238,7 +239,7 @@ class PodDeviceTest : BaseTest() {
val ble = mockk<DualApplePods>(relaxed = true) {
every { model } returns PodModel.AIRPODS_PRO3
every { this@mockk.address } returns bleRpa
every { meta } returns eu.darken.capod.pods.core.apple.ApplePods.AppleMeta(profile = profile)
every { meta } returns ApplePods.AppleMeta(profile = profile)
}
val device = PodDevice(ble = ble, aap = null)
device.address shouldBe bondedAddress
@@ -1,7 +1,7 @@
package eu.darken.capod.monitor.core
import eu.darken.capod.common.fromHex
import eu.darken.capod.pods.core.apple.protocol.RPAChecker
import eu.darken.capod.pods.core.apple.ble.protocol.RPAChecker
import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Test
import testhelpers.BaseTest
@@ -1,4 +1,4 @@
package eu.darken.capod.pods.core
package eu.darken.capod.pods.core.apple
import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Test
@@ -77,4 +77,4 @@ class ModelFeaturesTest : BaseTest() {
model.features.hasDualPods shouldBe true
}
}
}
}
@@ -1,9 +1,11 @@
package eu.darken.capod.pods.core.apple.protocol.aap
package eu.darken.capod.pods.core.apple.aap
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothSocket
import eu.darken.capod.common.bluetooth.l2cap.L2capSocketFactory
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.aap.protocol.AapCommand
import eu.darken.capod.pods.core.apple.aap.protocol.AapSetting
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.maps.shouldBeEmpty
import io.kotest.matchers.shouldBe
@@ -1,5 +1,6 @@
package eu.darken.capod.pods.core.apple.protocol.aap
package eu.darken.capod.pods.core.apple.aap
import eu.darken.capod.pods.core.apple.aap.protocol.AapSetting
import io.kotest.matchers.nulls.shouldBeNull
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe
@@ -35,7 +36,10 @@ class AapPodStateTest : BaseTest() {
val state = AapPodState(
connectionState = AapPodState.ConnectionState.READY,
settings = mapOf(
AapSetting.AncMode::class to AapSetting.AncMode(AapSetting.AncMode.Value.ON, listOf(AapSetting.AncMode.Value.ON)),
AapSetting.AncMode::class to AapSetting.AncMode(
AapSetting.AncMode.Value.ON,
listOf(AapSetting.AncMode.Value.ON)
),
)
)
val updated = state.withSetting(
@@ -51,7 +55,10 @@ class AapPodStateTest : BaseTest() {
val state = AapPodState(
connectionState = AapPodState.ConnectionState.READY,
settings = mapOf(
AapSetting.AncMode::class to AapSetting.AncMode(AapSetting.AncMode.Value.ON, listOf(AapSetting.AncMode.Value.ON)),
AapSetting.AncMode::class to AapSetting.AncMode(
AapSetting.AncMode.Value.ON,
listOf(AapSetting.AncMode.Value.ON)
),
)
)
val updated = state.withSetting(
@@ -75,9 +82,21 @@ class AapPodStateTest : BaseTest() {
fun `battery accessors map to correct types`() {
val state = AapPodState(
batteries = mapOf(
AapPodState.BatteryType.LEFT to AapPodState.Battery(AapPodState.BatteryType.LEFT, 0.8f, AapPodState.ChargingState.NOT_CHARGING),
AapPodState.BatteryType.RIGHT to AapPodState.Battery(AapPodState.BatteryType.RIGHT, 0.77f, AapPodState.ChargingState.CHARGING),
AapPodState.BatteryType.CASE to AapPodState.Battery(AapPodState.BatteryType.CASE, 0.48f, AapPodState.ChargingState.CHARGING),
AapPodState.BatteryType.LEFT to AapPodState.Battery(
AapPodState.BatteryType.LEFT,
0.8f,
AapPodState.ChargingState.NOT_CHARGING
),
AapPodState.BatteryType.RIGHT to AapPodState.Battery(
AapPodState.BatteryType.RIGHT,
0.77f,
AapPodState.ChargingState.CHARGING
),
AapPodState.BatteryType.CASE to AapPodState.Battery(
AapPodState.BatteryType.CASE,
0.48f,
AapPodState.ChargingState.CHARGING
),
),
)
state.batteryLeft shouldBe 0.8f
@@ -99,7 +118,11 @@ class AapPodStateTest : BaseTest() {
fun `headset battery for single-pod devices`() {
val state = AapPodState(
batteries = mapOf(
AapPodState.BatteryType.SINGLE to AapPodState.Battery(AapPodState.BatteryType.SINGLE, 0.6f, AapPodState.ChargingState.NOT_CHARGING),
AapPodState.BatteryType.SINGLE to AapPodState.Battery(
AapPodState.BatteryType.SINGLE,
0.6f,
AapPodState.ChargingState.NOT_CHARGING
),
),
)
state.batteryHeadset shouldBe 0.6f
@@ -110,7 +133,11 @@ class AapPodStateTest : BaseTest() {
fun `isCharging true for CHARGING state`() {
val state = AapPodState(
batteries = mapOf(
AapPodState.BatteryType.LEFT to AapPodState.Battery(AapPodState.BatteryType.LEFT, 0.8f, AapPodState.ChargingState.CHARGING),
AapPodState.BatteryType.LEFT to AapPodState.Battery(
AapPodState.BatteryType.LEFT,
0.8f,
AapPodState.ChargingState.CHARGING
),
),
)
state.isLeftCharging shouldBe true
@@ -120,7 +147,11 @@ class AapPodStateTest : BaseTest() {
fun `isCharging true for CHARGING_OPTIMIZED state`() {
val state = AapPodState(
batteries = mapOf(
AapPodState.BatteryType.LEFT to AapPodState.Battery(AapPodState.BatteryType.LEFT, 0.8f, AapPodState.ChargingState.CHARGING_OPTIMIZED),
AapPodState.BatteryType.LEFT to AapPodState.Battery(
AapPodState.BatteryType.LEFT,
0.8f,
AapPodState.ChargingState.CHARGING_OPTIMIZED
),
),
)
state.isLeftCharging shouldBe true
@@ -130,7 +161,11 @@ class AapPodStateTest : BaseTest() {
fun `isCharging false for NOT_CHARGING`() {
val state = AapPodState(
batteries = mapOf(
AapPodState.BatteryType.LEFT to AapPodState.Battery(AapPodState.BatteryType.LEFT, 0.8f, AapPodState.ChargingState.NOT_CHARGING),
AapPodState.BatteryType.LEFT to AapPodState.Battery(
AapPodState.BatteryType.LEFT,
0.8f,
AapPodState.ChargingState.NOT_CHARGING
),
),
)
state.isLeftCharging shouldBe false
@@ -1,22 +1,12 @@
package eu.darken.capod.pods.core.apple.protocol.aap
package eu.darken.capod.pods.core.apple.aap.devices
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.protocol.aap.AapPodState.BatteryType
import eu.darken.capod.pods.core.apple.protocol.aap.AapPodState.ChargingState
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.AncMode
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.AdaptiveAudioNoise
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.ConversationalAwareness
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.ConversationalAwarenessState
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.EndCallMuteMic
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.EndCallMuteMic.EndCallMode
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.EndCallMuteMic.MuteMicMode
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.NcWithOneAirPod
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.PersonalizedVolume
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.PressHoldDuration
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.PressSpeed
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.ToneVolume
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.VolumeSwipe
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.VolumeSwipeLength
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.aap.AapPodState
import eu.darken.capod.pods.core.apple.aap.protocol.AapCommand
import eu.darken.capod.pods.core.apple.aap.protocol.AapMessage
import eu.darken.capod.pods.core.apple.aap.protocol.AapSetting
import eu.darken.capod.pods.core.apple.aap.protocol.BaseAapSessionTest
import eu.darken.capod.pods.core.apple.aap.protocol.DefaultAapDeviceProfile
import io.kotest.matchers.collections.shouldContainExactly
import io.kotest.matchers.nulls.shouldBeNull
import io.kotest.matchers.nulls.shouldNotBeNull
@@ -66,10 +56,12 @@ class DefaultAapDeviceProfileTest : BaseAapSessionTest() {
@Nested
inner class InitExtTests {
@Test fun `returned for Pro 2`() { DefaultAapDeviceProfile(PodModel.AIRPODS_PRO2).encodeInitExt().shouldNotBeNull() }
@Test
fun `returned for Pro 2`() { DefaultAapDeviceProfile(PodModel.AIRPODS_PRO2).encodeInitExt().shouldNotBeNull() }
@Test fun `returned for Pro 3`() { DefaultAapDeviceProfile(PodModel.AIRPODS_PRO3).encodeInitExt().shouldNotBeNull() }
@Test fun `returned for AP4 ANC`() { DefaultAapDeviceProfile(PodModel.AIRPODS_GEN4_ANC).encodeInitExt().shouldNotBeNull() }
@Test fun `null for basic AirPods`() { DefaultAapDeviceProfile(PodModel.AIRPODS_GEN3).encodeInitExt().shouldBeNull() }
@Test
fun `null for basic AirPods`() { DefaultAapDeviceProfile(PodModel.AIRPODS_GEN3).encodeInitExt().shouldBeNull() }
@Test fun `null for Pro 1`() { DefaultAapDeviceProfile(PodModel.AIRPODS_PRO).encodeInitExt().shouldBeNull() }
@Test fun `null for Max`() { DefaultAapDeviceProfile(PodModel.AIRPODS_MAX).encodeInitExt().shouldBeNull() }
@@ -83,25 +75,25 @@ class DefaultAapDeviceProfileTest : BaseAapSessionTest() {
@Nested
inner class SupportedAncModesTests {
private fun ancModesFor(model: PodModel): List<AncMode.Value> {
private fun ancModesFor(model: PodModel): List<AapSetting.AncMode.Value> {
val p = DefaultAapDeviceProfile(model)
return decodeSetting<AncMode>(settingsMessage(0x0D, 0x02)).let {
return decodeSetting<AapSetting.AncMode>(settingsMessage(0x0D, 0x02)).let {
// Can't use the outer profile, need per-model profile
(p.decodeSetting(settingsMessage(0x0D, 0x02))!!.second as AncMode).supported
(p.decodeSetting(settingsMessage(0x0D, 0x02))!!.second as AapSetting.AncMode).supported
}
}
@Test
fun `Pro 2 supports ON, TRANSPARENCY, ADAPTIVE`() {
ancModesFor(PodModel.AIRPODS_PRO2) shouldContainExactly listOf(
AncMode.Value.ON, AncMode.Value.TRANSPARENCY, AncMode.Value.ADAPTIVE,
AapSetting.AncMode.Value.ON, AapSetting.AncMode.Value.TRANSPARENCY, AapSetting.AncMode.Value.ADAPTIVE,
)
}
@Test
fun `Pro 1 supports ON, TRANSPARENCY only`() {
ancModesFor(PodModel.AIRPODS_PRO) shouldContainExactly listOf(
AncMode.Value.ON, AncMode.Value.TRANSPARENCY,
AapSetting.AncMode.Value.ON, AapSetting.AncMode.Value.TRANSPARENCY,
)
}
@@ -113,7 +105,7 @@ class DefaultAapDeviceProfileTest : BaseAapSessionTest() {
@Test
fun `Max supports ON, TRANSPARENCY only`() {
ancModesFor(PodModel.AIRPODS_MAX) shouldContainExactly listOf(
AncMode.Value.ON, AncMode.Value.TRANSPARENCY,
AapSetting.AncMode.Value.ON, AapSetting.AncMode.Value.TRANSPARENCY,
)
}
}
@@ -122,22 +114,26 @@ class DefaultAapDeviceProfileTest : BaseAapSessionTest() {
@Nested
inner class AncModeTests {
@Test fun `encode OFF`() { profile.encodeCommand(AapCommand.SetAncMode(AncMode.Value.OFF))[7] shouldBe 0x01.toByte() }
@Test fun `encode ON`() { profile.encodeCommand(AapCommand.SetAncMode(AncMode.Value.ON))[7] shouldBe 0x02.toByte() }
@Test fun `encode TRANSPARENCY`() { profile.encodeCommand(AapCommand.SetAncMode(AncMode.Value.TRANSPARENCY))[7] shouldBe 0x03.toByte() }
@Test fun `encode ADAPTIVE`() { profile.encodeCommand(AapCommand.SetAncMode(AncMode.Value.ADAPTIVE))[7] shouldBe 0x04.toByte() }
@Test fun `encode OFF`() { profile.encodeCommand(AapCommand.SetAncMode(AapSetting.AncMode.Value.OFF))[7] shouldBe 0x01.toByte() }
@Test fun `encode ON`() { profile.encodeCommand(AapCommand.SetAncMode(AapSetting.AncMode.Value.ON))[7] shouldBe 0x02.toByte() }
@Test fun `encode TRANSPARENCY`() { profile.encodeCommand(AapCommand.SetAncMode(AapSetting.AncMode.Value.TRANSPARENCY))[7] shouldBe 0x03.toByte() }
@Test fun `encode ADAPTIVE`() { profile.encodeCommand(AapCommand.SetAncMode(AapSetting.AncMode.Value.ADAPTIVE))[7] shouldBe 0x04.toByte() }
@Test fun `decode OFF`() { decodeSetting<AncMode>(settingsMessage(0x0D, 0x01)).current shouldBe AncMode.Value.OFF }
@Test fun `decode ON`() { decodeSetting<AncMode>(settingsMessage(0x0D, 0x02)).current shouldBe AncMode.Value.ON }
@Test fun `decode TRANSPARENCY`() { decodeSetting<AncMode>(settingsMessage(0x0D, 0x03)).current shouldBe AncMode.Value.TRANSPARENCY }
@Test fun `decode ADAPTIVE`() { decodeSetting<AncMode>(settingsMessage(0x0D, 0x04)).current shouldBe AncMode.Value.ADAPTIVE }
@Test
fun `decode OFF`() { decodeSetting<AapSetting.AncMode>(settingsMessage(0x0D, 0x01)).current shouldBe AapSetting.AncMode.Value.OFF }
@Test
fun `decode ON`() { decodeSetting<AapSetting.AncMode>(settingsMessage(0x0D, 0x02)).current shouldBe AapSetting.AncMode.Value.ON }
@Test
fun `decode TRANSPARENCY`() { decodeSetting<AapSetting.AncMode>(settingsMessage(0x0D, 0x03)).current shouldBe AapSetting.AncMode.Value.TRANSPARENCY }
@Test
fun `decode ADAPTIVE`() { decodeSetting<AapSetting.AncMode>(settingsMessage(0x0D, 0x04)).current shouldBe AapSetting.AncMode.Value.ADAPTIVE }
@Test fun `decode unknown wire value returns null`() { profile.decodeSetting(settingsMessage(0x0D, 0x99)).shouldBeNull() }
@Test
fun `round-trip all modes`() {
for (mode in AncMode.Value.entries) {
for (mode in AapSetting.AncMode.Value.entries) {
val encoded = profile.encodeCommand(AapCommand.SetAncMode(mode))
val decoded = decodeSetting<AncMode>(AapMessage.parse(encoded)!!)
val decoded = decodeSetting<AapSetting.AncMode>(AapMessage.Companion.parse(encoded)!!)
decoded.current shouldBe mode
}
}
@@ -149,8 +145,8 @@ class DefaultAapDeviceProfileTest : BaseAapSessionTest() {
inner class ConversationalAwarenessTests {
@Test fun `encode enabled`() { profile.encodeCommand(AapCommand.SetConversationalAwareness(true))[7] shouldBe 0x01.toByte() }
@Test fun `encode disabled`() { profile.encodeCommand(AapCommand.SetConversationalAwareness(false))[7] shouldBe 0x02.toByte() }
@Test fun `decode enabled`() { decodeSetting<ConversationalAwareness>(settingsMessage(0x28, 0x01)).enabled shouldBe true }
@Test fun `decode disabled`() { decodeSetting<ConversationalAwareness>(settingsMessage(0x28, 0x02)).enabled shouldBe false }
@Test fun `decode enabled`() { decodeSetting<AapSetting.ConversationalAwareness>(settingsMessage(0x28, 0x01)).enabled shouldBe true }
@Test fun `decode disabled`() { decodeSetting<AapSetting.ConversationalAwareness>(settingsMessage(0x28, 0x02)).enabled shouldBe false }
@Test fun `decode unknown value returns null`() { profile.decodeSetting(settingsMessage(0x28, 0x00)).shouldBeNull() }
}
@@ -158,18 +154,20 @@ class DefaultAapDeviceProfileTest : BaseAapSessionTest() {
@Nested
inner class PressSpeedTests {
@Test fun `encode default`() { profile.encodeCommand(AapCommand.SetPressSpeed(PressSpeed.Value.DEFAULT))[7] shouldBe 0x00.toByte() }
@Test fun `encode slower`() { profile.encodeCommand(AapCommand.SetPressSpeed(PressSpeed.Value.SLOWER))[7] shouldBe 0x01.toByte() }
@Test fun `encode slowest`() { profile.encodeCommand(AapCommand.SetPressSpeed(PressSpeed.Value.SLOWEST))[7] shouldBe 0x02.toByte() }
@Test fun `decode default`() { decodeSetting<PressSpeed>(settingsMessage(0x17, 0x00)).value shouldBe PressSpeed.Value.DEFAULT }
@Test fun `decode slower`() { decodeSetting<PressSpeed>(settingsMessage(0x17, 0x01)).value shouldBe PressSpeed.Value.SLOWER }
@Test fun `encode default`() { profile.encodeCommand(AapCommand.SetPressSpeed(AapSetting.PressSpeed.Value.DEFAULT))[7] shouldBe 0x00.toByte() }
@Test fun `encode slower`() { profile.encodeCommand(AapCommand.SetPressSpeed(AapSetting.PressSpeed.Value.SLOWER))[7] shouldBe 0x01.toByte() }
@Test fun `encode slowest`() { profile.encodeCommand(AapCommand.SetPressSpeed(AapSetting.PressSpeed.Value.SLOWEST))[7] shouldBe 0x02.toByte() }
@Test
fun `decode default`() { decodeSetting<AapSetting.PressSpeed>(settingsMessage(0x17, 0x00)).value shouldBe AapSetting.PressSpeed.Value.DEFAULT }
@Test
fun `decode slower`() { decodeSetting<AapSetting.PressSpeed>(settingsMessage(0x17, 0x01)).value shouldBe AapSetting.PressSpeed.Value.SLOWER }
@Test fun `decode unknown returns null`() { profile.decodeSetting(settingsMessage(0x17, 0x99)).shouldBeNull() }
@Test
fun `round-trip all values`() {
for (v in PressSpeed.Value.entries) {
for (v in AapSetting.PressSpeed.Value.entries) {
val encoded = profile.encodeCommand(AapCommand.SetPressSpeed(v))
decodeSetting<PressSpeed>(AapMessage.parse(encoded)!!).value shouldBe v
decodeSetting<AapSetting.PressSpeed>(AapMessage.Companion.parse(encoded)!!).value shouldBe v
}
}
}
@@ -178,14 +176,16 @@ class DefaultAapDeviceProfileTest : BaseAapSessionTest() {
@Nested
inner class PressHoldDurationTests {
@Test fun `encode shorter`() { profile.encodeCommand(AapCommand.SetPressHoldDuration(PressHoldDuration.Value.SHORTER))[7] shouldBe 0x01.toByte() }
@Test fun `decode shortest`() { decodeSetting<PressHoldDuration>(settingsMessage(0x18, 0x02)).value shouldBe PressHoldDuration.Value.SHORTEST }
@Test
fun `encode shorter`() { profile.encodeCommand(AapCommand.SetPressHoldDuration(AapSetting.PressHoldDuration.Value.SHORTER))[7] shouldBe 0x01.toByte() }
@Test
fun `decode shortest`() { decodeSetting<AapSetting.PressHoldDuration>(settingsMessage(0x18, 0x02)).value shouldBe AapSetting.PressHoldDuration.Value.SHORTEST }
@Test
fun `round-trip all values`() {
for (v in PressHoldDuration.Value.entries) {
for (v in AapSetting.PressHoldDuration.Value.entries) {
val encoded = profile.encodeCommand(AapCommand.SetPressHoldDuration(v))
decodeSetting<PressHoldDuration>(AapMessage.parse(encoded)!!).value shouldBe v
decodeSetting<AapSetting.PressHoldDuration>(AapMessage.Companion.parse(encoded)!!).value shouldBe v
}
}
}
@@ -196,8 +196,8 @@ class DefaultAapDeviceProfileTest : BaseAapSessionTest() {
inner class NcOneAirPodTests {
@Test fun `encode enabled`() { profile.encodeCommand(AapCommand.SetNcWithOneAirPod(true))[7] shouldBe 0x01.toByte() }
@Test fun `encode disabled`() { profile.encodeCommand(AapCommand.SetNcWithOneAirPod(false))[7] shouldBe 0x02.toByte() }
@Test fun `decode enabled`() { decodeSetting<NcWithOneAirPod>(settingsMessage(0x1B, 0x01)).enabled shouldBe true }
@Test fun `decode disabled`() { decodeSetting<NcWithOneAirPod>(settingsMessage(0x1B, 0x02)).enabled shouldBe false }
@Test fun `decode enabled`() { decodeSetting<AapSetting.NcWithOneAirPod>(settingsMessage(0x1B, 0x01)).enabled shouldBe true }
@Test fun `decode disabled`() { decodeSetting<AapSetting.NcWithOneAirPod>(settingsMessage(0x1B, 0x02)).enabled shouldBe false }
@Test fun `decode unknown returns null`() { profile.decodeSetting(settingsMessage(0x1B, 0x00)).shouldBeNull() }
}
@@ -208,22 +208,24 @@ class DefaultAapDeviceProfileTest : BaseAapSessionTest() {
@Test fun `encode level 50`() { profile.encodeCommand(AapCommand.SetToneVolume(50))[7] shouldBe 50.toByte() }
@Test fun `encode clamps to min 15`() { profile.encodeCommand(AapCommand.SetToneVolume(0))[7] shouldBe 0x0F.toByte() }
@Test fun `encode clamps to max 100`() { profile.encodeCommand(AapCommand.SetToneVolume(200))[7] shouldBe 0x64.toByte() }
@Test fun `decode level`() { decodeSetting<ToneVolume>(settingsMessage(0x1F, 50)).level shouldBe 50 }
@Test fun `decode level`() { decodeSetting<AapSetting.ToneVolume>(settingsMessage(0x1F, 50)).level shouldBe 50 }
}
// ── Volume Swipe Length ──────────────────────────────────
@Nested
inner class VolumeSwipeLengthTests {
@Test fun `encode longest`() { profile.encodeCommand(AapCommand.SetVolumeSwipeLength(VolumeSwipeLength.Value.LONGEST))[7] shouldBe 0x02.toByte() }
@Test fun `decode longer`() { decodeSetting<VolumeSwipeLength>(settingsMessage(0x23, 0x01)).value shouldBe VolumeSwipeLength.Value.LONGER }
@Test
fun `encode longest`() { profile.encodeCommand(AapCommand.SetVolumeSwipeLength(AapSetting.VolumeSwipeLength.Value.LONGEST))[7] shouldBe 0x02.toByte() }
@Test
fun `decode longer`() { decodeSetting<AapSetting.VolumeSwipeLength>(settingsMessage(0x23, 0x01)).value shouldBe AapSetting.VolumeSwipeLength.Value.LONGER }
@Test fun `decode unknown returns null`() { profile.decodeSetting(settingsMessage(0x23, 0x99)).shouldBeNull() }
@Test
fun `round-trip all values`() {
for (v in VolumeSwipeLength.Value.entries) {
for (v in AapSetting.VolumeSwipeLength.Value.entries) {
val encoded = profile.encodeCommand(AapCommand.SetVolumeSwipeLength(v))
decodeSetting<VolumeSwipeLength>(AapMessage.parse(encoded)!!).value shouldBe v
decodeSetting<AapSetting.VolumeSwipeLength>(AapMessage.Companion.parse(encoded)!!).value shouldBe v
}
}
}
@@ -233,7 +235,7 @@ class DefaultAapDeviceProfileTest : BaseAapSessionTest() {
@Nested
inner class VolumeSwipeTests {
@Test fun `encode enabled`() { profile.encodeCommand(AapCommand.SetVolumeSwipe(true))[7] shouldBe 0x01.toByte() }
@Test fun `decode disabled`() { decodeSetting<VolumeSwipe>(settingsMessage(0x25, 0x02)).enabled shouldBe false }
@Test fun `decode disabled`() { decodeSetting<AapSetting.VolumeSwipe>(settingsMessage(0x25, 0x02)).enabled shouldBe false }
@Test fun `decode unknown returns null`() { profile.decodeSetting(settingsMessage(0x25, 0x00)).shouldBeNull() }
}
@@ -242,7 +244,7 @@ class DefaultAapDeviceProfileTest : BaseAapSessionTest() {
@Nested
inner class PersonalizedVolumeTests {
@Test fun `encode enabled`() { profile.encodeCommand(AapCommand.SetPersonalizedVolume(true))[7] shouldBe 0x01.toByte() }
@Test fun `decode disabled`() { decodeSetting<PersonalizedVolume>(settingsMessage(0x26, 0x02)).enabled shouldBe false }
@Test fun `decode disabled`() { decodeSetting<AapSetting.PersonalizedVolume>(settingsMessage(0x26, 0x02)).enabled shouldBe false }
@Test fun `decode unknown returns null`() { profile.decodeSetting(settingsMessage(0x26, 0x00)).shouldBeNull() }
}
@@ -253,7 +255,7 @@ class DefaultAapDeviceProfileTest : BaseAapSessionTest() {
@Test fun `encode level 50`() { profile.encodeCommand(AapCommand.SetAdaptiveAudioNoise(50))[7] shouldBe 50.toByte() }
@Test fun `encode clamps to 0`() { profile.encodeCommand(AapCommand.SetAdaptiveAudioNoise(-5))[7] shouldBe 0x00.toByte() }
@Test fun `encode clamps to 100`() { profile.encodeCommand(AapCommand.SetAdaptiveAudioNoise(150))[7] shouldBe 0x64.toByte() }
@Test fun `decode level`() { decodeSetting<AdaptiveAudioNoise>(settingsMessage(0x2E, 64)).level shouldBe 64 }
@Test fun `decode level`() { decodeSetting<AapSetting.AdaptiveAudioNoise>(settingsMessage(0x2E, 64)).level shouldBe 64 }
}
// ── EndCall / MuteMic ────────────────────────────────────
@@ -262,7 +264,7 @@ class DefaultAapDeviceProfileTest : BaseAapSessionTest() {
inner class EndCallMuteMicTests {
@Test
fun `encode single press mute, double press end call`() {
val bytes = profile.encodeCommand(AapCommand.SetEndCallMuteMic(MuteMicMode.SINGLE_PRESS, EndCallMode.DOUBLE_PRESS))
val bytes = profile.encodeCommand(AapCommand.SetEndCallMuteMic(AapSetting.EndCallMuteMic.MuteMicMode.SINGLE_PRESS, AapSetting.EndCallMuteMic.EndCallMode.DOUBLE_PRESS))
bytes[6] shouldBe 0x24.toByte()
bytes[7] shouldBe 0x21.toByte()
bytes[8] shouldBe 0x23.toByte()
@@ -271,30 +273,30 @@ class DefaultAapDeviceProfileTest : BaseAapSessionTest() {
@Test
fun `decode standard format 0x21`() {
val ecm = decodeSetting<EndCallMuteMic>(aapMessage("04 00 04 00 09 00 24 21 22 03 00"))
ecm.muteMic shouldBe MuteMicMode.DOUBLE_PRESS
ecm.endCall shouldBe EndCallMode.SINGLE_PRESS
val ecm = decodeSetting<AapSetting.EndCallMuteMic>(aapMessage("04 00 04 00 09 00 24 21 22 03 00"))
ecm.muteMic shouldBe AapSetting.EndCallMuteMic.MuteMicMode.DOUBLE_PRESS
ecm.endCall shouldBe AapSetting.EndCallMuteMic.EndCallMode.SINGLE_PRESS
}
@Test
fun `decode compact format 0x20 combined 0x02`() {
val ecm = decodeSetting<EndCallMuteMic>(aapMessage("04 00 04 00 09 00 24 20 02 00 00"))
ecm.muteMic shouldBe MuteMicMode.SINGLE_PRESS
ecm.endCall shouldBe EndCallMode.DOUBLE_PRESS
val ecm = decodeSetting<AapSetting.EndCallMuteMic>(aapMessage("04 00 04 00 09 00 24 20 02 00 00"))
ecm.muteMic shouldBe AapSetting.EndCallMuteMic.MuteMicMode.SINGLE_PRESS
ecm.endCall shouldBe AapSetting.EndCallMuteMic.EndCallMode.DOUBLE_PRESS
}
@Test
fun `decode compact format 0x20 combined 0x03`() {
val ecm = decodeSetting<EndCallMuteMic>(aapMessage("04 00 04 00 09 00 24 20 03 00 00"))
ecm.muteMic shouldBe MuteMicMode.DOUBLE_PRESS
ecm.endCall shouldBe EndCallMode.SINGLE_PRESS
val ecm = decodeSetting<AapSetting.EndCallMuteMic>(aapMessage("04 00 04 00 09 00 24 20 03 00 00"))
ecm.muteMic shouldBe AapSetting.EndCallMuteMic.MuteMicMode.DOUBLE_PRESS
ecm.endCall shouldBe AapSetting.EndCallMuteMic.EndCallMode.SINGLE_PRESS
}
@Test
fun `decode compact format subtype 0x00 from real Pro 3`() {
val ecm = decodeSetting<EndCallMuteMic>(aapMessage("04 00 04 00 09 00 24 00 03 00 00"))
ecm.muteMic shouldBe MuteMicMode.DOUBLE_PRESS
ecm.endCall shouldBe EndCallMode.SINGLE_PRESS
val ecm = decodeSetting<AapSetting.EndCallMuteMic>(aapMessage("04 00 04 00 09 00 24 00 03 00 00"))
ecm.muteMic shouldBe AapSetting.EndCallMuteMic.MuteMicMode.DOUBLE_PRESS
ecm.endCall shouldBe AapSetting.EndCallMuteMic.EndCallMode.SINGLE_PRESS
}
@Test
@@ -307,8 +309,8 @@ class DefaultAapDeviceProfileTest : BaseAapSessionTest() {
@Nested
inner class ConversationAwarenessStateTests {
@Test fun `speaking start`() { decodeSetting<ConversationalAwarenessState>(aapMessage("04 00 04 00 4B 00 01")).speaking shouldBe true }
@Test fun `speaking stop`() { decodeSetting<ConversationalAwarenessState>(aapMessage("04 00 04 00 4B 00 04")).speaking shouldBe false }
@Test fun `speaking start`() { decodeSetting<AapSetting.ConversationalAwarenessState>(aapMessage("04 00 04 00 4B 00 01")).speaking shouldBe true }
@Test fun `speaking stop`() { decodeSetting<AapSetting.ConversationalAwarenessState>(aapMessage("04 00 04 00 4B 00 04")).speaking shouldBe false }
@Test fun `empty payload returns null`() { profile.decodeSetting(aapMessage("04 00 04 00 4B 00")).shouldBeNull() }
}
@@ -319,8 +321,8 @@ class DefaultAapDeviceProfileTest : BaseAapSessionTest() {
@Test
fun `decode single left pod`() {
val r = profile.decodeBattery(batteryMessage(0x01, 0x04, 0x00, 85, 0x02, 0x00))!!
r[BatteryType.LEFT]!!.percent shouldBe 0.85f
r[BatteryType.LEFT]!!.charging shouldBe ChargingState.NOT_CHARGING
r[AapPodState.BatteryType.LEFT]!!.percent shouldBe 0.85f
r[AapPodState.BatteryType.LEFT]!!.charging shouldBe AapPodState.ChargingState.NOT_CHARGING
}
@Test
@@ -332,17 +334,17 @@ class DefaultAapDeviceProfileTest : BaseAapSessionTest() {
0x08, 0x00, 50, 0x02, 0x00,
))!!
r.size shouldBe 3
r[BatteryType.RIGHT]!!.percent shouldBe 0.9f
r[BatteryType.RIGHT]!!.charging shouldBe ChargingState.CHARGING
r[BatteryType.LEFT]!!.percent shouldBe 0.8f
r[BatteryType.CASE]!!.percent shouldBe 0.5f
r[AapPodState.BatteryType.RIGHT]!!.percent shouldBe 0.9f
r[AapPodState.BatteryType.RIGHT]!!.charging shouldBe AapPodState.ChargingState.CHARGING
r[AapPodState.BatteryType.LEFT]!!.percent shouldBe 0.8f
r[AapPodState.BatteryType.CASE]!!.percent shouldBe 0.5f
}
@Test
fun `decode headset (single)`() {
val r = profile.decodeBattery(batteryMessage(0x01, 0x01, 0x00, 60, 0x04, 0x00))!!
r[BatteryType.SINGLE]!!.percent shouldBe 0.6f
r[BatteryType.SINGLE]!!.charging shouldBe ChargingState.DISCONNECTED
r[AapPodState.BatteryType.SINGLE]!!.percent shouldBe 0.6f
r[AapPodState.BatteryType.SINGLE]!!.charging shouldBe AapPodState.ChargingState.DISCONNECTED
}
@Test fun `skips percent above 100`() { profile.decodeBattery(batteryMessage(0x01, 0x04, 0x00, 127, 0x02, 0x00))!! shouldBe emptyMap() }
@@ -351,22 +353,24 @@ class DefaultAapDeviceProfileTest : BaseAapSessionTest() {
@Test fun `skips unknown type`() { profile.decodeBattery(batteryMessage(0x01, 0x10, 0x00, 50, 0x02, 0x00))!! shouldBe emptyMap() }
@Test fun `handles truncated entry`() { profile.decodeBattery(batteryMessage(0x01, 0x04, 0x00, 50))!! shouldBe emptyMap() }
@Test fun `non-battery message returns null`() { profile.decodeBattery(settingsMessage(0x0D, 0x02)).shouldBeNull() }
@Test fun `zero percent is valid`() { profile.decodeBattery(batteryMessage(0x01, 0x04, 0x00, 0, 0x01, 0x00))!![BatteryType.LEFT]!!.percent shouldBe 0f }
@Test fun `100 percent is valid`() { profile.decodeBattery(batteryMessage(0x01, 0x04, 0x00, 100, 0x02, 0x00))!![BatteryType.LEFT]!!.percent shouldBe 1f }
@Test
fun `zero percent is valid`() { profile.decodeBattery(batteryMessage(0x01, 0x04, 0x00, 0, 0x01, 0x00))!![AapPodState.BatteryType.LEFT]!!.percent shouldBe 0f }
@Test
fun `100 percent is valid`() { profile.decodeBattery(batteryMessage(0x01, 0x04, 0x00, 100, 0x02, 0x00))!![AapPodState.BatteryType.LEFT]!!.percent shouldBe 1f }
@Test
fun `CHARGING_OPTIMIZED state 0x05`() {
val r = profile.decodeBattery(batteryMessage(0x03, 0x04, 0x01, 80, 0x05, 0x01, 0x02, 0x01, 79, 0x05, 0x01, 0x08, 0x01, 48, 0x01, 0x01))!!
r[BatteryType.LEFT]!!.charging shouldBe ChargingState.CHARGING_OPTIMIZED
r[BatteryType.RIGHT]!!.charging shouldBe ChargingState.CHARGING_OPTIMIZED
r[BatteryType.CASE]!!.charging shouldBe ChargingState.CHARGING
r[AapPodState.BatteryType.LEFT]!!.charging shouldBe AapPodState.ChargingState.CHARGING_OPTIMIZED
r[AapPodState.BatteryType.RIGHT]!!.charging shouldBe AapPodState.ChargingState.CHARGING_OPTIMIZED
r[AapPodState.BatteryType.CASE]!!.charging shouldBe AapPodState.ChargingState.CHARGING
}
@Test
fun `pods out of case, case disconnected`() {
val r = profile.decodeBattery(batteryMessage(0x03, 0x04, 0x01, 80, 0x02, 0x01, 0x02, 0x01, 77, 0x02, 0x01, 0x08, 0x01, 0, 0x04, 0x01))!!
r[BatteryType.CASE]!!.percent shouldBe 0f
r[BatteryType.CASE]!!.charging shouldBe ChargingState.DISCONNECTED
r[AapPodState.BatteryType.CASE]!!.percent shouldBe 0f
r[AapPodState.BatteryType.CASE]!!.charging shouldBe AapPodState.ChargingState.DISCONNECTED
}
@Test
@@ -374,9 +378,9 @@ class DefaultAapDeviceProfileTest : BaseAapSessionTest() {
val msg = aapMessage("04 00 04 00 04 00 03 04 01 50 05 01 02 01 4F 05 01 08 01 30 01 01")
val r = profile.decodeBattery(msg)!!
r.size shouldBe 3
r[BatteryType.LEFT]!!.percent shouldBe 0.8f
r[BatteryType.RIGHT]!!.percent shouldBe 0.79f
r[BatteryType.CASE]!!.percent shouldBe 0.48f
r[AapPodState.BatteryType.LEFT]!!.percent shouldBe 0.8f
r[AapPodState.BatteryType.RIGHT]!!.percent shouldBe 0.79f
r[AapPodState.BatteryType.CASE]!!.percent shouldBe 0.48f
}
}
@@ -397,7 +401,7 @@ class DefaultAapDeviceProfileTest : BaseAapSessionTest() {
val enc = ByteArray(16) { 0x22.toByte() }
val payload = byteArrayOf(0x02, 0x01, 0x00, 16, 0x00, *irk, 0x04, 0x00, 16, 0x00, *enc)
val raw = byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x31, 0x00) + payload
val result = profile.decodePrivateKeyResponse(AapMessage.parse(raw)!!)!!
val result = profile.decodePrivateKeyResponse(AapMessage.Companion.parse(raw)!!)!!
result.irk shouldBe irk
result.encKey shouldBe enc
}
@@ -406,7 +410,7 @@ class DefaultAapDeviceProfileTest : BaseAapSessionTest() {
fun `decode response with only IRK`() {
val irk = ByteArray(16) { 0xAA.toByte() }
val raw = byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x31, 0x00, 0x01, 0x01, 0x00, 16, 0x00, *irk)
val result = profile.decodePrivateKeyResponse(AapMessage.parse(raw)!!)!!
val result = profile.decodePrivateKeyResponse(AapMessage.Companion.parse(raw)!!)!!
result.irk shouldBe irk
result.encKey.shouldBeNull()
}
@@ -421,4 +425,4 @@ class DefaultAapDeviceProfileTest : BaseAapSessionTest() {
@Test fun `unknown setting ID returns null`() { profile.decodeSetting(settingsMessage(0x7F, 0x01)).shouldBeNull() }
@Test fun `non-settings command returns null`() { profile.decodeSetting(aapMessage("04 00 04 00 1D 00 01 02 03 04")).shouldBeNull() }
@Test fun `payload too short returns null`() { profile.decodeSetting(aapMessage("04 00 04 00 09 00 0D")).shouldBeNull() }
}
}
@@ -1,22 +1,9 @@
package eu.darken.capod.pods.core.apple.protocol.aap
package eu.darken.capod.pods.core.apple.aap.devices.airpods
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.protocol.aap.AapPodState.BatteryType
import eu.darken.capod.pods.core.apple.protocol.aap.AapPodState.ChargingState
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.AdaptiveAudioNoise
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.AncMode
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.ConversationalAwareness
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.ConversationalAwarenessState
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.EndCallMuteMic
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.EndCallMuteMic.EndCallMode
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.EndCallMuteMic.MuteMicMode
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.NcWithOneAirPod
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.PersonalizedVolume
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.PressHoldDuration
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.PressSpeed
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.ToneVolume
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.VolumeSwipe
import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting.VolumeSwipeLength
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.aap.AapPodState
import eu.darken.capod.pods.core.apple.aap.protocol.AapSetting
import eu.darken.capod.pods.core.apple.aap.protocol.BaseAapSessionTest
import io.kotest.matchers.collections.shouldContainExactly
import io.kotest.matchers.nulls.shouldBeNull
import io.kotest.matchers.shouldBe
@@ -79,18 +66,18 @@ class AirPodsPro3AapSessionTest : BaseAapSessionTest() {
fun `in case charging - pods CHARGING_OPTIMIZED, case CHARGING`() {
val r =
profile.decodeBattery(aapMessage("04 00 04 00 04 00 03 04 01 50 05 01 02 01 4F 05 01 08 01 30 01 01"))!!
r[BatteryType.LEFT]!!.let { it.percent shouldBe 0.8f; it.charging shouldBe ChargingState.CHARGING_OPTIMIZED }
r[BatteryType.RIGHT]!!.let { it.percent shouldBe 0.79f; it.charging shouldBe ChargingState.CHARGING_OPTIMIZED }
r[BatteryType.CASE]!!.let { it.percent shouldBe 0.48f; it.charging shouldBe ChargingState.CHARGING }
r[AapPodState.BatteryType.LEFT]!!.let { it.percent shouldBe 0.8f; it.charging shouldBe AapPodState.ChargingState.CHARGING_OPTIMIZED }
r[AapPodState.BatteryType.RIGHT]!!.let { it.percent shouldBe 0.79f; it.charging shouldBe AapPodState.ChargingState.CHARGING_OPTIMIZED }
r[AapPodState.BatteryType.CASE]!!.let { it.percent shouldBe 0.48f; it.charging shouldBe AapPodState.ChargingState.CHARGING }
}
@Test
fun `out of case - pods NOT_CHARGING, case DISCONNECTED`() {
val r =
profile.decodeBattery(aapMessage("04 00 04 00 04 00 03 02 01 4D 02 01 04 01 50 02 01 08 01 00 04 01"))!!
r[BatteryType.RIGHT]!!.charging shouldBe ChargingState.NOT_CHARGING
r[BatteryType.LEFT]!!.charging shouldBe ChargingState.NOT_CHARGING
r[BatteryType.CASE]!!.charging shouldBe ChargingState.DISCONNECTED
r[AapPodState.BatteryType.RIGHT]!!.charging shouldBe AapPodState.ChargingState.NOT_CHARGING
r[AapPodState.BatteryType.LEFT]!!.charging shouldBe AapPodState.ChargingState.NOT_CHARGING
r[AapPodState.BatteryType.CASE]!!.charging shouldBe AapPodState.ChargingState.DISCONNECTED
}
}
@@ -116,57 +103,57 @@ class AirPodsPro3AapSessionTest : BaseAapSessionTest() {
@Nested
inner class SettingsSessionTests {
@Test fun `ANC mode ON`() {
val anc = decodeSetting<AncMode>("04 00 04 00 09 00 0D 02 00 00 00")
anc.current shouldBe AncMode.Value.ON
val anc = decodeSetting<AapSetting.AncMode>("04 00 04 00 09 00 0D 02 00 00 00")
anc.current shouldBe AapSetting.AncMode.Value.ON
anc.supported shouldContainExactly listOf(
AncMode.Value.ON,
AncMode.Value.TRANSPARENCY,
AncMode.Value.ADAPTIVE
AapSetting.AncMode.Value.ON,
AapSetting.AncMode.Value.TRANSPARENCY,
AapSetting.AncMode.Value.ADAPTIVE
)
}
@Test fun `press hold duration DEFAULT`() {
decodeSetting<PressHoldDuration>("04 00 04 00 09 00 18 00 00 00 00").value shouldBe PressHoldDuration.Value.DEFAULT
decodeSetting<AapSetting.PressHoldDuration>("04 00 04 00 09 00 18 00 00 00 00").value shouldBe AapSetting.PressHoldDuration.Value.DEFAULT
}
@Test fun `press speed DEFAULT`() {
decodeSetting<PressSpeed>("04 00 04 00 09 00 17 00 00 00 00").value shouldBe PressSpeed.Value.DEFAULT
decodeSetting<AapSetting.PressSpeed>("04 00 04 00 09 00 17 00 00 00 00").value shouldBe AapSetting.PressSpeed.Value.DEFAULT
}
@Test fun `volume swipe ON`() {
decodeSetting<VolumeSwipe>("04 00 04 00 09 00 25 01 00 00 00").enabled shouldBe true
decodeSetting<AapSetting.VolumeSwipe>("04 00 04 00 09 00 25 01 00 00 00").enabled shouldBe true
}
@Test fun `volume swipe length DEFAULT`() {
decodeSetting<VolumeSwipeLength>("04 00 04 00 09 00 23 00 00 00 00").value shouldBe VolumeSwipeLength.Value.DEFAULT
decodeSetting<AapSetting.VolumeSwipeLength>("04 00 04 00 09 00 23 00 00 00 00").value shouldBe AapSetting.VolumeSwipeLength.Value.DEFAULT
}
@Test fun `tone volume 80`() {
decodeSetting<ToneVolume>("04 00 04 00 09 00 1F 50 50 00 00").level shouldBe 0x50
decodeSetting<AapSetting.ToneVolume>("04 00 04 00 09 00 1F 50 50 00 00").level shouldBe 0x50
}
@Test fun `end call mute mic - subtype 0x00`() {
decodeSetting<EndCallMuteMic>("04 00 04 00 09 00 24 00 03 00 00").let { it.muteMic shouldBe MuteMicMode.DOUBLE_PRESS; it.endCall shouldBe EndCallMode.SINGLE_PRESS }
decodeSetting<AapSetting.EndCallMuteMic>("04 00 04 00 09 00 24 00 03 00 00").let { it.muteMic shouldBe AapSetting.EndCallMuteMic.MuteMicMode.DOUBLE_PRESS; it.endCall shouldBe AapSetting.EndCallMuteMic.EndCallMode.SINGLE_PRESS }
}
@Test fun `conversational awareness OFF`() {
decodeSetting<ConversationalAwareness>("04 00 04 00 09 00 28 02 00 00 00").enabled shouldBe false
decodeSetting<AapSetting.ConversationalAwareness>("04 00 04 00 09 00 28 02 00 00 00").enabled shouldBe false
}
@Test fun `conversational awareness ON after reconnect`() {
decodeSetting<ConversationalAwareness>("04 00 04 00 09 00 28 01 00 00 00").enabled shouldBe true
decodeSetting<AapSetting.ConversationalAwareness>("04 00 04 00 09 00 28 01 00 00 00").enabled shouldBe true
}
@Test fun `personalized volume OFF`() {
decodeSetting<PersonalizedVolume>("04 00 04 00 09 00 26 02 00 00 00").enabled shouldBe false
decodeSetting<AapSetting.PersonalizedVolume>("04 00 04 00 09 00 26 02 00 00 00").enabled shouldBe false
}
@Test fun `adaptive audio noise 50`() {
decodeSetting<AdaptiveAudioNoise>("04 00 04 00 09 00 2E 32 00 00 00").level shouldBe 0x32
decodeSetting<AapSetting.AdaptiveAudioNoise>("04 00 04 00 09 00 2E 32 00 00 00").level shouldBe 0x32
}
@Test fun `NC one airpod OFF`() {
decodeSetting<NcWithOneAirPod>("04 00 04 00 09 00 1B 02 00 00 00").enabled shouldBe false
decodeSetting<AapSetting.NcWithOneAirPod>("04 00 04 00 09 00 1B 02 00 00 00").enabled shouldBe false
}
}
@@ -175,11 +162,11 @@ class AirPodsPro3AapSessionTest : BaseAapSessionTest() {
@Nested
inner class AncModeSwitchingTests {
@Test fun `device echoes TRANSPARENCY`() {
decodeSetting<AncMode>("04 00 04 00 09 00 0D 03 00 00 00").current shouldBe AncMode.Value.TRANSPARENCY
decodeSetting<AapSetting.AncMode>("04 00 04 00 09 00 0D 03 00 00 00").current shouldBe AapSetting.AncMode.Value.TRANSPARENCY
}
@Test fun `device echoes ADAPTIVE`() {
decodeSetting<AncMode>("04 00 04 00 09 00 0D 04 00 00 00").current shouldBe AncMode.Value.ADAPTIVE
decodeSetting<AapSetting.AncMode>("04 00 04 00 09 00 0D 04 00 00 00").current shouldBe AapSetting.AncMode.Value.ADAPTIVE
}
}
@@ -187,7 +174,7 @@ class AirPodsPro3AapSessionTest : BaseAapSessionTest() {
@Test
fun `conversation awareness state - not speaking`() {
decodeSetting<ConversationalAwarenessState>("04 00 04 00 4B 00 00").speaking shouldBe false
decodeSetting<AapSetting.ConversationalAwarenessState>("04 00 04 00 4B 00 00").speaking shouldBe false
}
// ── Unhandled Messages ───────────────────────────────────
@@ -210,4 +197,4 @@ class AirPodsPro3AapSessionTest : BaseAapSessionTest() {
}
}
}
}
}
@@ -1,4 +1,4 @@
package eu.darken.capod.pods.core.apple.protocol.aap
package eu.darken.capod.pods.core.apple.aap.protocol
import io.kotest.matchers.collections.shouldBeEmpty
import io.kotest.matchers.collections.shouldHaveSize
@@ -1,4 +1,4 @@
package eu.darken.capod.pods.core.apple.protocol.aap
package eu.darken.capod.pods.core.apple.aap.protocol
import io.kotest.matchers.nulls.shouldBeNull
import io.kotest.matchers.nulls.shouldNotBeNull
@@ -1,11 +1,11 @@
package eu.darken.capod.pods.core.apple.protocol.aap
package eu.darken.capod.pods.core.apple.aap.protocol
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.PodModel
import testhelpers.BaseTest
/**
* Shared base for AAP protocol tests. Provides hex parsing, message builders,
* and type-safe setting extraction mirrors [eu.darken.capod.pods.core.apple.BaseBlePodsTest]
* and type-safe setting extraction mirrors [eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest]
* for the BLE path.
*
* Subclasses set [podModel] to get a correctly-configured [profile].
@@ -1,9 +1,11 @@
package eu.darken.capod.pods.core.apple
package eu.darken.capod.pods.core.apple.ble
import eu.darken.capod.pods.core.BlePodSnapshot
import eu.darken.capod.pods.core.apple.airpods.AirPodsGen1
import eu.darken.capod.pods.core.apple.airpods.AirPodsPro
import eu.darken.capod.pods.core.apple.misc.UnknownAppleSnapshotBle
import eu.darken.capod.pods.core.apple.ble.devices.ApplePods
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.ble.devices.DualApplePods
import eu.darken.capod.pods.core.apple.ble.devices.airpods.AirPodsGen1
import eu.darken.capod.pods.core.apple.ble.devices.airpods.AirPodsPro
import eu.darken.capod.pods.core.apple.ble.devices.misc.UnknownAppleSnapshotBle
import io.kotest.matchers.shouldBe
import io.kotest.matchers.types.instanceOf
import kotlinx.coroutines.test.runTest
@@ -1,4 +1,4 @@
package eu.darken.capod.pods.core.apple
package eu.darken.capod.pods.core.apple.ble.devices
import dagger.BindsInstance
import dagger.Component
@@ -6,8 +6,10 @@ import eu.darken.capod.common.SystemClockWrap
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.fromHex
import eu.darken.capod.common.serialization.SerializationModule
import eu.darken.capod.pods.core.BlePodSnapshot
import eu.darken.capod.pods.core.apple.protocol.ContinuityProtocol
import eu.darken.capod.pods.core.apple.ble.AppleFactory
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
import eu.darken.capod.pods.core.apple.ble.AppleFactoryModule
import eu.darken.capod.pods.core.apple.ble.protocol.ContinuityProtocol
import eu.darken.capod.profiles.core.DeviceProfile
import eu.darken.capod.profiles.core.DeviceProfilesRepo
import io.mockk.MockKAnnotations
@@ -1,4 +1,4 @@
package eu.darken.capod.pods.core.apple
package eu.darken.capod.pods.core.apple.ble.devices
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
@@ -1,6 +1,6 @@
package eu.darken.capod.pods.core.apple
package eu.darken.capod.pods.core.apple.ble.devices
import eu.darken.capod.pods.core.apple.airpods.HasStateDetectionAirPods
import eu.darken.capod.pods.core.apple.ble.devices.airpods.HasStateDetectionAirPods
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -1,4 +1,4 @@
package eu.darken.capod.pods.core.apple
package eu.darken.capod.pods.core.apple.ble.devices
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
@@ -1,9 +1,9 @@
package eu.darken.capod.pods.core.apple.airpods
package eu.darken.capod.pods.core.apple.ble.devices.airpods
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.DualApplePods
import eu.darken.capod.pods.core.apple.HasAppleColor
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.ble.devices.DualApplePods
import eu.darken.capod.pods.core.apple.ble.devices.HasAppleColor
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -1,9 +1,9 @@
package eu.darken.capod.pods.core.apple.airpods
package eu.darken.capod.pods.core.apple.ble.devices.airpods
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.DualApplePods
import eu.darken.capod.pods.core.apple.HasAppleColor
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.ble.devices.DualApplePods
import eu.darken.capod.pods.core.apple.ble.devices.HasAppleColor
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -1,9 +1,9 @@
package eu.darken.capod.pods.core.apple.airpods
package eu.darken.capod.pods.core.apple.ble.devices.airpods
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.DualApplePods
import eu.darken.capod.pods.core.apple.HasAppleColor
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.ble.devices.DualApplePods
import eu.darken.capod.pods.core.apple.ble.devices.HasAppleColor
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -1,9 +1,9 @@
package eu.darken.capod.pods.core.apple.airpods
package eu.darken.capod.pods.core.apple.ble.devices.airpods
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.DualApplePods
import eu.darken.capod.pods.core.apple.HasAppleColor
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.ble.devices.DualApplePods
import eu.darken.capod.pods.core.apple.ble.devices.HasAppleColor
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -1,9 +1,9 @@
package eu.darken.capod.pods.core.apple.airpods
package eu.darken.capod.pods.core.apple.ble.devices.airpods
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.DualApplePods
import eu.darken.capod.pods.core.apple.HasAppleColor
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.ble.devices.DualApplePods
import eu.darken.capod.pods.core.apple.ble.devices.HasAppleColor
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -1,9 +1,9 @@
package eu.darken.capod.pods.core.apple.airpods
package eu.darken.capod.pods.core.apple.ble.devices.airpods
import eu.darken.capod.common.isBitSet
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.HasAppleColor
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.ble.devices.HasAppleColor
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -1,7 +1,7 @@
package eu.darken.capod.pods.core.apple.airpods
package eu.darken.capod.pods.core.apple.ble.devices.airpods
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -1,8 +1,8 @@
package eu.darken.capod.pods.core.apple.airpods
package eu.darken.capod.pods.core.apple.ble.devices.airpods
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.HasAppleColor
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.ble.devices.HasAppleColor
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -1,8 +1,8 @@
package eu.darken.capod.pods.core.apple.airpods
package eu.darken.capod.pods.core.apple.ble.devices.airpods
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.HasAppleColor
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.ble.devices.HasAppleColor
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -1,8 +1,8 @@
package eu.darken.capod.pods.core.apple.airpods
package eu.darken.capod.pods.core.apple.ble.devices.airpods
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.HasAppleColor
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.ble.devices.HasAppleColor
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -1,9 +1,9 @@
package eu.darken.capod.pods.core.apple.airpods
package eu.darken.capod.pods.core.apple.ble.devices.airpods
import eu.darken.capod.common.toHex
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.HasAppleColor
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.ble.devices.HasAppleColor
import eu.darken.capod.profiles.core.AppleDeviceProfile
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
@@ -1,8 +1,8 @@
package eu.darken.capod.pods.core.apple.beats
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.HasAppleColor
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.ble.devices.HasAppleColor
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -1,7 +1,7 @@
package eu.darken.capod.pods.core.apple.beats
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -1,7 +1,7 @@
package eu.darken.capod.pods.core.apple.beats
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -1,7 +1,7 @@
package eu.darken.capod.pods.core.apple.beats
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -1,7 +1,7 @@
package eu.darken.capod.pods.core.apple.beats
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -1,7 +1,7 @@
package eu.darken.capod.pods.core.apple.beats
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -1,8 +1,8 @@
package eu.darken.capod.pods.core.apple.beats
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.airpods.HasStateDetectionAirPods
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.ble.devices.airpods.HasStateDetectionAirPods
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -1,8 +1,8 @@
package eu.darken.capod.pods.core.apple.beats
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.HasAppleColor
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.ble.devices.HasAppleColor
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -1,8 +1,8 @@
package eu.darken.capod.pods.core.apple.beats
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.HasAppleColor
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.ble.devices.HasAppleColor
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -1,7 +1,7 @@
package eu.darken.capod.pods.core.apple.misc
package eu.darken.capod.pods.core.apple.ble.devices.misc
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -1,7 +1,7 @@
package eu.darken.capod.pods.core.apple.misc
package eu.darken.capod.pods.core.apple.ble.devices.misc
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -1,7 +1,7 @@
package eu.darken.capod.pods.core.apple.misc
package eu.darken.capod.pods.core.apple.ble.devices.misc
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -1,7 +1,7 @@
package eu.darken.capod.pods.core.apple.misc
package eu.darken.capod.pods.core.apple.ble.devices.misc
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -1,7 +1,7 @@
package eu.darken.capod.pods.core.apple.misc
package eu.darken.capod.pods.core.apple.ble.devices.misc
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseBlePodsTest
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -3,16 +3,14 @@ package eu.darken.capod.reaction.core.aap
import eu.darken.capod.common.bluetooth.BluetoothDevice2
import eu.darken.capod.common.bluetooth.BluetoothManager2
import eu.darken.capod.monitor.core.BlePodMonitor
import eu.darken.capod.pods.core.BlePodSnapshot
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.protocol.aap.AapConnectionManager
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.aap.AapConnectionManager
import eu.darken.capod.pods.core.apple.protocol.aap.AapPodState
import eu.darken.capod.pods.core.apple.aap.AapPodState
import eu.darken.capod.profiles.core.AppleDeviceProfile
import eu.darken.capod.profiles.core.DeviceProfile
import eu.darken.capod.profiles.core.DeviceProfilesRepo
import io.kotest.matchers.shouldBe
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
@@ -29,7 +27,6 @@ import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import testhelpers.BaseTest
import java.io.IOException
@OptIn(ExperimentalCoroutinesApi::class)
class AapAutoConnectTest : BaseTest() {
@@ -1,6 +1,6 @@
package eu.darken.capod.reaction.core.autoconnect
import eu.darken.capod.pods.core.apple.DualApplePods
import eu.darken.capod.pods.core.apple.ble.devices.DualApplePods
import io.kotest.matchers.shouldBe
import io.mockk.mockk
import org.junit.jupiter.api.BeforeEach
@@ -1,6 +1,6 @@
package eu.darken.capod.reaction.core.popup
import eu.darken.capod.pods.core.apple.DualApplePods
import eu.darken.capod.pods.core.apple.ble.devices.DualApplePods
import io.kotest.matchers.shouldBe
import io.mockk.mockk
import org.junit.jupiter.api.BeforeEach