From 725b41e6d7c3995d73063ed56ff14754621240c2 Mon Sep 17 00:00:00 2001 From: darken Date: Wed, 15 Apr 2026 07:33:47 +0200 Subject: [PATCH] feat(device-settings): Merge one-pod mode settings into a single toggle Combine reaction one-pod mode (autoplay/autopause) and firmware NC-with-one-AirPod into one setting. The merged toggle always sets the app-local flag and additionally syncs the firmware NC setting via AAP when connected. Also remove redundant @OptIn(ExperimentalCoroutinesApi::class) annotations from tests, already covered by the global compiler opt-in flag. --- .../ui/devicesettings/DeviceSettingsScreen.kt | 17 +- .../devicesettings/DeviceSettingsViewModel.kt | 11 +- .../monitor/core/aap/AapLifecycleManager.kt | 2 + .../capod/monitor/core/aap/NcOnePodSender.kt | 58 ++++ app/src/main/res/values/strings.xml | 2 +- .../DeviceSettingsViewModelTest.kt | 2 - .../main/ui/overview/OverviewViewModelTest.kt | 2 - .../capod/monitor/core/DeviceMonitorTest.kt | 2 - .../monitor/core/aap/AapAutoConnectTest.kt | 2 - .../monitor/core/aap/NcOnePodSenderTest.kt | 255 ++++++++++++++++++ .../apple/aap/AapConnectionManagerTest.kt | 2 - 11 files changed, 326 insertions(+), 29 deletions(-) create mode 100644 app/src/main/java/eu/darken/capod/monitor/core/aap/NcOnePodSender.kt create mode 100644 app/src/test/java/eu/darken/capod/monitor/core/aap/NcOnePodSenderTest.kt diff --git a/app/src/main/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsScreen.kt b/app/src/main/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsScreen.kt index 6c43e76e..972333bd 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsScreen.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsScreen.kt @@ -150,7 +150,6 @@ fun DeviceSettingsScreenHost( onNavigateUp = { vm.navUp() }, onAncModeChange = { vm.setAncMode(it) }, onConversationalAwarenessChange = { vm.setConversationalAwareness(it) }, - onNcWithOneAirPodChange = { vm.setNcWithOneAirPod(it) }, onPersonalizedVolumeChange = { vm.setPersonalizedVolume(it) }, onToneVolumeChange = { vm.setToneVolume(it) }, onAdaptiveAudioNoiseChange = { vm.setAdaptiveAudioNoise(it) }, @@ -187,7 +186,6 @@ fun DeviceSettingsScreen( onNavigateUp: () -> Unit, onAncModeChange: (AapSetting.AncMode.Value) -> Unit = {}, onConversationalAwarenessChange: (Boolean) -> Unit = {}, - onNcWithOneAirPodChange: (Boolean) -> Unit = {}, onPersonalizedVolumeChange: (Boolean) -> Unit = {}, onToneVolumeChange: (Int) -> Unit = {}, onAdaptiveAudioNoiseChange: (Int) -> Unit = {}, @@ -325,7 +323,8 @@ fun DeviceSettingsScreen( if (features.hasDualPods) { val onePodModeActive = reactions.autoPlay || reactions.autoPause || - (reactions.autoConnect && reactions.autoConnectCondition == AutoConnectCondition.IN_EAR) + (reactions.autoConnect && reactions.autoConnectCondition == AutoConnectCondition.IN_EAR) || + features.hasNcOneAirpod SettingsBaseItem( title = stringResource(R.string.settings_onepod_mode_label), subtitle = stringResource(R.string.settings_onepod_mode_description), @@ -450,7 +449,6 @@ fun DeviceSettingsScreen( // ── Noise Control ──────────────────────────── val ancMode = device.ancMode - val ncOneAirpod = device.ncWithOneAirPod val adaptiveNoise = device.adaptiveAudioNoise if (features.hasAncControl && ancMode != null) { val cycleMask = if (features.hasListeningModeCycle) { @@ -471,7 +469,6 @@ fun DeviceSettingsScreen( enabled = enabled, ) val hasNoiseExtras = (features.hasAdaptiveAudioNoise && adaptiveNoise != null) || - (features.hasNcOneAirpod && ncOneAirpod != null) || (!isPro && features.hasListeningModeCycle) if (hasNoiseExtras) { HorizontalDivider( @@ -487,16 +484,6 @@ fun DeviceSettingsScreen( isAdaptiveMode = ancMode.current == AapSetting.AncMode.Value.ADAPTIVE, ) } - if (features.hasNcOneAirpod && ncOneAirpod != null) { - SettingsSwitchItem( - icon = Icons.TwoTone.Headphones, - title = stringResource(R.string.device_settings_nc_one_airpod_label), - subtitle = stringResource(R.string.device_settings_nc_one_airpod_description), - checked = ncOneAirpod.enabled, - onCheckedChange = onNcWithOneAirPodChange, - enabled = enabled, - ) - } if (!isPro && features.hasListeningModeCycle) { SettingsBaseItem( icon = Icons.TwoTone.Loop, diff --git a/app/src/main/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsViewModel.kt b/app/src/main/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsViewModel.kt index a64e1a74..401fc542 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsViewModel.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsViewModel.kt @@ -206,8 +206,6 @@ class DeviceSettingsViewModel @Inject constructor( fun setConversationalAwareness(enabled: Boolean) = send(AapCommand.SetConversationalAwareness(enabled)) - fun setNcWithOneAirPod(enabled: Boolean) = send(AapCommand.SetNcWithOneAirPod(enabled)) - fun setPersonalizedVolume(enabled: Boolean) = send(AapCommand.SetPersonalizedVolume(enabled)) fun setToneVolume(level: Int) = send(AapCommand.SetToneVolume(level)) @@ -299,7 +297,14 @@ class DeviceSettingsViewModel @Inject constructor( fun setOnePodMode(enabled: Boolean) { log(TAG, INFO) { "setOnePodMode($enabled)" } - launch { updateProfileNow { it.copy(onePodMode = enabled) } } + launch { + updateProfileNow { it.copy(onePodMode = enabled) } + // Opportunistic immediate sync — NcOnePodSender handles deferred/reconnect + val device = deviceMonitor.getDeviceForProfile(targetProfileId.value ?: return@launch) + if (device != null && device.isAapConnected && device.model.features.hasNcOneAirpod) { + sendInternal(AapCommand.SetNcWithOneAirPod(enabled)) + } + } } fun setAutoPlay(enabled: Boolean) = launch { diff --git a/app/src/main/java/eu/darken/capod/monitor/core/aap/AapLifecycleManager.kt b/app/src/main/java/eu/darken/capod/monitor/core/aap/AapLifecycleManager.kt index f2e9040b..60b9a01f 100644 --- a/app/src/main/java/eu/darken/capod/monitor/core/aap/AapLifecycleManager.kt +++ b/app/src/main/java/eu/darken/capod/monitor/core/aap/AapLifecycleManager.kt @@ -26,6 +26,7 @@ class AapLifecycleManager @Inject constructor( private val aapKeyPersister: AapKeyPersister, private val stemConfigSender: StemConfigSender, private val stemPressReaction: StemPressReaction, + private val ncOnePodSender: NcOnePodSender, ) { fun start() { log(TAG) { "start()" } @@ -34,6 +35,7 @@ class AapLifecycleManager @Inject constructor( aapKeyPersister.monitor(), stemConfigSender.monitor(), stemPressReaction.monitor(), + ncOnePodSender.monitor(), ) .catch { e -> log(TAG, WARN) { "AAP lifecycle error: ${e.asLog()}" } } .setupCommonEventHandlers(TAG) { "aapActive" } diff --git a/app/src/main/java/eu/darken/capod/monitor/core/aap/NcOnePodSender.kt b/app/src/main/java/eu/darken/capod/monitor/core/aap/NcOnePodSender.kt new file mode 100644 index 00000000..0ca2508e --- /dev/null +++ b/app/src/main/java/eu/darken/capod/monitor/core/aap/NcOnePodSender.kt @@ -0,0 +1,58 @@ +package eu.darken.capod.monitor.core.aap + +import eu.darken.capod.common.debug.logging.Logging.Priority.WARN +import eu.darken.capod.common.debug.logging.log +import eu.darken.capod.common.debug.logging.logTag +import eu.darken.capod.common.flow.setupCommonEventHandlers +import eu.darken.capod.pods.core.apple.aap.AapConnectionManager +import eu.darken.capod.pods.core.apple.aap.AapPodState +import eu.darken.capod.pods.core.apple.aap.protocol.AapCommand +import eu.darken.capod.profiles.core.AppleDeviceProfile +import eu.darken.capod.profiles.core.DeviceProfilesRepo +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class NcOnePodSender @Inject constructor( + private val aapManager: AapConnectionManager, + private val profilesRepo: DeviceProfilesRepo, +) { + fun monitor(): Flow = combine( + aapManager.allStates, + profilesRepo.profiles, + ) { states, profiles -> + val appleProfiles = profiles.filterIsInstance() + states.entries + .filter { (_, s) -> s.connectionState == AapPodState.ConnectionState.READY } + .mapNotNull { (address, _) -> + val profile = appleProfiles.firstOrNull { it.address == address } + if (profile != null && profile.model.features.hasNcOneAirpod) { + address to profile.onePodMode + } else { + null + } + } + } + .distinctUntilChanged() + .onEach { commands -> + for ((address, enabled) in commands) { + try { + aapManager.sendCommand(address, AapCommand.SetNcWithOneAirPod(enabled)) + log(TAG) { "Sent SetNcWithOneAirPod($enabled) to $address" } + } catch (e: Exception) { + log(TAG, WARN) { "SetNcWithOneAirPod send failed for $address: $e" } + } + } + } + .map { } + .setupCommonEventHandlers(TAG) { "ncOnePod" } + + companion object { + private val TAG = logTag("Monitor", "NcOnePodSender") + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 74a8dacd..d49a6b2b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -81,7 +81,7 @@ Don\'t let the system group collected BLE data before forwarding it to us. One pod mode - Treat a single in-ear pod as worn. Auto play/pause and "In ear" auto-connect react to either pod instead of requiring both. + Auto play/pause, auto-connect, and on supported models noise cancellation, react to a single pod instead of requiring both. Show case popup Show a popup when the device case is opened (experimental). Show connection popup diff --git a/app/src/test/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsViewModelTest.kt b/app/src/test/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsViewModelTest.kt index a78a3fc6..f7458409 100644 --- a/app/src/test/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsViewModelTest.kt +++ b/app/src/test/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsViewModelTest.kt @@ -22,7 +22,6 @@ import io.mockk.mockk import io.mockk.verify import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow @@ -45,7 +44,6 @@ import testhelpers.coroutine.TestDispatcherProvider import testhelpers.datastore.FakeDataStoreValue import testhelpers.livedata.InstantExecutorExtension -@OptIn(ExperimentalCoroutinesApi::class) @ExtendWith(InstantExecutorExtension::class) class DeviceSettingsViewModelTest : BaseTest() { diff --git a/app/src/test/java/eu/darken/capod/main/ui/overview/OverviewViewModelTest.kt b/app/src/test/java/eu/darken/capod/main/ui/overview/OverviewViewModelTest.kt index 25bc39fa..a5940862 100644 --- a/app/src/test/java/eu/darken/capod/main/ui/overview/OverviewViewModelTest.kt +++ b/app/src/test/java/eu/darken/capod/main/ui/overview/OverviewViewModelTest.kt @@ -19,7 +19,6 @@ import io.mockk.every import io.mockk.mockk import io.mockk.verify import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map @@ -39,7 +38,6 @@ import testhelpers.coroutine.TestDispatcherProvider import testhelpers.datastore.FakeDataStoreValue import testhelpers.livedata.InstantExecutorExtension -@OptIn(ExperimentalCoroutinesApi::class) @ExtendWith(InstantExecutorExtension::class) class OverviewViewModelTest : BaseTest() { diff --git a/app/src/test/java/eu/darken/capod/monitor/core/DeviceMonitorTest.kt b/app/src/test/java/eu/darken/capod/monitor/core/DeviceMonitorTest.kt index d590c2dc..ffa37278 100644 --- a/app/src/test/java/eu/darken/capod/monitor/core/DeviceMonitorTest.kt +++ b/app/src/test/java/eu/darken/capod/monitor/core/DeviceMonitorTest.kt @@ -23,7 +23,6 @@ import io.mockk.coVerify import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.MutableStateFlow @@ -37,7 +36,6 @@ import testhelpers.BaseTest import testhelpers.TestTimeSource import java.time.Instant -@OptIn(ExperimentalCoroutinesApi::class) class DeviceMonitorTest : BaseTest() { private val testDispatcher = UnconfinedTestDispatcher() diff --git a/app/src/test/java/eu/darken/capod/monitor/core/aap/AapAutoConnectTest.kt b/app/src/test/java/eu/darken/capod/monitor/core/aap/AapAutoConnectTest.kt index 7f2b1b3b..46c654c1 100644 --- a/app/src/test/java/eu/darken/capod/monitor/core/aap/AapAutoConnectTest.kt +++ b/app/src/test/java/eu/darken/capod/monitor/core/aap/AapAutoConnectTest.kt @@ -16,7 +16,6 @@ import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.mockk -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf @@ -30,7 +29,6 @@ import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import testhelpers.BaseTest -@OptIn(ExperimentalCoroutinesApi::class) class AapAutoConnectTest : BaseTest() { private val testDispatcher = UnconfinedTestDispatcher() diff --git a/app/src/test/java/eu/darken/capod/monitor/core/aap/NcOnePodSenderTest.kt b/app/src/test/java/eu/darken/capod/monitor/core/aap/NcOnePodSenderTest.kt new file mode 100644 index 00000000..5a6907bd --- /dev/null +++ b/app/src/test/java/eu/darken/capod/monitor/core/aap/NcOnePodSenderTest.kt @@ -0,0 +1,255 @@ +package eu.darken.capod.monitor.core.aap + +import eu.darken.capod.pods.core.apple.PodModel +import eu.darken.capod.pods.core.apple.aap.AapConnectionManager +import eu.darken.capod.pods.core.apple.aap.AapPodState +import eu.darken.capod.pods.core.apple.aap.protocol.AapCommand +import eu.darken.capod.profiles.core.AppleDeviceProfile +import eu.darken.capod.profiles.core.DeviceProfile +import eu.darken.capod.profiles.core.DeviceProfilesRepo +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest + +class NcOnePodSenderTest : BaseTest() { + + private val testDispatcher = UnconfinedTestDispatcher() + + private lateinit var aapManager: AapConnectionManager + private lateinit var profilesRepo: DeviceProfilesRepo + + private lateinit var profilesFlow: MutableStateFlow> + private lateinit var allStatesFlow: MutableStateFlow> + + private val testAddress = "AA:BB:CC:DD:EE:FF" + private val testAddress2 = "11:22:33:44:55:66" + + private val proProfile = AppleDeviceProfile( + label = "AirPods Pro 3", + model = PodModel.AIRPODS_PRO3, + address = testAddress, + onePodMode = false, + ) + + private val nonNcProfile = AppleDeviceProfile( + label = "AirPods Gen 3", + model = PodModel.UNKNOWN, + address = testAddress2, + onePodMode = true, + ) + + @BeforeEach + fun setup() { + profilesFlow = MutableStateFlow(emptyList()) + allStatesFlow = MutableStateFlow(emptyMap()) + + aapManager = mockk(relaxed = true) { + every { allStates } returns allStatesFlow + } + + profilesRepo = mockk(relaxUnitFun = true) { + every { profiles } returns profilesFlow + } + } + + private fun createSender() = NcOnePodSender( + aapManager = aapManager, + profilesRepo = profilesRepo, + ) + + @Test + fun `sends enabled when READY and onePodMode is true`() = runTest(testDispatcher) { + val sender = createSender() + val job = launch { sender.monitor().toList() } + + profilesFlow.value = listOf(proProfile.copy(onePodMode = true)) + allStatesFlow.value = mapOf( + testAddress to AapPodState(connectionState = AapPodState.ConnectionState.READY) + ) + advanceUntilIdle() + + coVerify(exactly = 1) { + aapManager.sendCommand(testAddress, AapCommand.SetNcWithOneAirPod(true)) + } + + job.cancel() + } + + @Test + fun `sends disabled when READY and onePodMode is false`() = runTest(testDispatcher) { + val sender = createSender() + val job = launch { sender.monitor().toList() } + + profilesFlow.value = listOf(proProfile.copy(onePodMode = false)) + allStatesFlow.value = mapOf( + testAddress to AapPodState(connectionState = AapPodState.ConnectionState.READY) + ) + advanceUntilIdle() + + coVerify(exactly = 1) { + aapManager.sendCommand(testAddress, AapCommand.SetNcWithOneAirPod(false)) + } + + job.cancel() + } + + @Test + fun `does not send when device does not support NC one airpod`() = runTest(testDispatcher) { + val sender = createSender() + val job = launch { sender.monitor().toList() } + + profilesFlow.value = listOf(nonNcProfile) + allStatesFlow.value = mapOf( + testAddress2 to AapPodState(connectionState = AapPodState.ConnectionState.READY) + ) + advanceUntilIdle() + + coVerify(exactly = 0) { aapManager.sendCommand(any(), any()) } + + job.cancel() + } + + @Test + fun `does not send when connection is not READY`() = runTest(testDispatcher) { + val sender = createSender() + val job = launch { sender.monitor().toList() } + + profilesFlow.value = listOf(proProfile.copy(onePodMode = true)) + allStatesFlow.value = mapOf( + testAddress to AapPodState(connectionState = AapPodState.ConnectionState.CONNECTING) + ) + advanceUntilIdle() + + coVerify(exactly = 0) { aapManager.sendCommand(any(), any()) } + + job.cancel() + } + + @Test + fun `sends again when onePodMode toggles`() = runTest(testDispatcher) { + val sender = createSender() + val job = launch { sender.monitor().toList() } + + allStatesFlow.value = mapOf( + testAddress to AapPodState(connectionState = AapPodState.ConnectionState.READY) + ) + + // Enable + profilesFlow.value = listOf(proProfile.copy(onePodMode = true)) + advanceUntilIdle() + + // Disable + profilesFlow.value = listOf(proProfile.copy(onePodMode = false)) + advanceUntilIdle() + + coVerify(ordering = io.mockk.Ordering.ORDERED) { + aapManager.sendCommand(testAddress, AapCommand.SetNcWithOneAirPod(true)) + aapManager.sendCommand(testAddress, AapCommand.SetNcWithOneAirPod(false)) + } + + job.cancel() + } + + @Test + fun `does not resend for unrelated state changes`() = runTest(testDispatcher) { + val sender = createSender() + val job = launch { sender.monitor().toList() } + + profilesFlow.value = listOf(proProfile.copy(onePodMode = true)) + allStatesFlow.value = mapOf( + testAddress to AapPodState(connectionState = AapPodState.ConnectionState.READY) + ) + advanceUntilIdle() + + // Unrelated AAP state change (e.g. battery update) — same READY, same profile + allStatesFlow.value = mapOf( + testAddress to AapPodState( + connectionState = AapPodState.ConnectionState.READY, + lastMessageAt = java.time.Instant.now(), + ) + ) + advanceUntilIdle() + + // Should only have sent once — distinctUntilChanged filters the second emission + coVerify(exactly = 1) { + aapManager.sendCommand(testAddress, AapCommand.SetNcWithOneAirPod(true)) + } + + job.cancel() + } + + @Test + fun `handles send failure without crashing the flow`() = runTest(testDispatcher) { + coEvery { + aapManager.sendCommand(any(), any()) + } throws RuntimeException("Connection lost") + + val sender = createSender() + val job = launch { sender.monitor().toList() } + + profilesFlow.value = listOf(proProfile.copy(onePodMode = true)) + allStatesFlow.value = mapOf( + testAddress to AapPodState(connectionState = AapPodState.ConnectionState.READY) + ) + advanceUntilIdle() + + // Flow should survive the exception + coVerify(exactly = 1) { + aapManager.sendCommand(testAddress, AapCommand.SetNcWithOneAirPod(true)) + } + + job.cancel() + } + + @Test + fun `does not send to unmatched addresses`() = runTest(testDispatcher) { + val sender = createSender() + val job = launch { sender.monitor().toList() } + + // Profile for testAddress, but AAP connection on a different address + profilesFlow.value = listOf(proProfile) + allStatesFlow.value = mapOf( + "XX:XX:XX:XX:XX:XX" to AapPodState(connectionState = AapPodState.ConnectionState.READY) + ) + advanceUntilIdle() + + coVerify(exactly = 0) { aapManager.sendCommand(any(), any()) } + + job.cancel() + } + + @Test + fun `only sends to NC-capable device when multiple devices connected`() = runTest(testDispatcher) { + val sender = createSender() + val job = launch { sender.monitor().toList() } + + profilesFlow.value = listOf( + proProfile.copy(onePodMode = true), + nonNcProfile, + ) + allStatesFlow.value = mapOf( + testAddress to AapPodState(connectionState = AapPodState.ConnectionState.READY), + testAddress2 to AapPodState(connectionState = AapPodState.ConnectionState.READY), + ) + advanceUntilIdle() + + coVerify(exactly = 1) { + aapManager.sendCommand(testAddress, AapCommand.SetNcWithOneAirPod(true)) + } + coVerify(exactly = 0) { + aapManager.sendCommand(testAddress2, any()) + } + + job.cancel() + } +} diff --git a/app/src/test/java/eu/darken/capod/pods/core/apple/aap/AapConnectionManagerTest.kt b/app/src/test/java/eu/darken/capod/pods/core/apple/aap/AapConnectionManagerTest.kt index e16f07c8..6789a4a0 100644 --- a/app/src/test/java/eu/darken/capod/pods/core/apple/aap/AapConnectionManagerTest.kt +++ b/app/src/test/java/eu/darken/capod/pods/core/apple/aap/AapConnectionManagerTest.kt @@ -12,7 +12,6 @@ import io.kotest.matchers.maps.shouldBeEmpty import io.kotest.matchers.shouldBe import io.mockk.every import io.mockk.mockk -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.advanceUntilIdle @@ -26,7 +25,6 @@ import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.IOException -@OptIn(ExperimentalCoroutinesApi::class) class AapConnectionManagerTest : BaseTest() { private val testDispatcher = UnconfinedTestDispatcher()