diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/PodModel.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/PodModel.kt
index 0d23321f..4317cfb6 100644
--- a/app/src/main/java/eu/darken/capod/pods/core/apple/PodModel.kt
+++ b/app/src/main/java/eu/darken/capod/pods/core/apple/PodModel.kt
@@ -22,7 +22,9 @@ enum class PodModel(
Features(
hasDualPods = true,
hasCase = true,
+ hasEarDetection = true,
hasMicrophoneMode = true,
+ hasEarDetectionToggle = true,
),
modelNumbers = setOf("A1523", "A1722"), // L/R earphones
leftPodIconRes = R.drawable.device_airpods_gen1_left,
@@ -37,7 +39,9 @@ enum class PodModel(
Features(
hasDualPods = true,
hasCase = true,
+ hasEarDetection = true,
hasMicrophoneMode = true,
+ hasEarDetectionToggle = true,
),
modelNumbers = setOf("A2031", "A2032"), // L/R earphones
leftPodIconRes = R.drawable.device_airpods_gen1_left,
@@ -52,10 +56,13 @@ enum class PodModel(
Features(
hasDualPods = true,
hasCase = true,
+ hasEarDetection = true,
hasPressSpeed = true,
hasPressHoldDuration = true,
hasToneVolume = true,
+ hasEndCallMuteMic = true,
hasMicrophoneMode = true,
+ hasEarDetectionToggle = true,
),
modelNumbers = setOf("A2564", "A2565"), // L/R earphones
leftPodIconRes = R.drawable.device_airpods_gen3_left,
@@ -74,6 +81,7 @@ enum class PodModel(
hasPressSpeed = true,
hasPressHoldDuration = true,
hasToneVolume = true,
+ hasEndCallMuteMic = true,
hasMicrophoneMode = true,
hasEarDetectionToggle = true,
hasSleepDetection = true,
@@ -314,7 +322,6 @@ enum class PodModel(
"Beats Solo Pro",
R.drawable.device_beats_headphones,
Features(
- hasEarDetection = true,
hasAncControl = true,
),
modelNumbers = setOf("A1881"), // headphones
@@ -343,7 +350,6 @@ enum class PodModel(
"Beats Studio 3",
R.drawable.device_beats_studio3,
Features(
- hasEarDetection = true,
hasAncControl = true,
),
modelNumbers = setOf("A1914"), // headphones
@@ -410,6 +416,7 @@ enum class PodModel(
hasDualPods = true,
hasCase = true,
hasEarDetection = true,
+ hasEarDetectionToggle = true,
),
modelNumbers = setOf("A2047", "A2048", "A2453", "A2454"), // L/R earbuds, 2019 + 2020 revisions
leftPodIconRes = R.drawable.device_powerbeats_pro_left,
@@ -426,6 +433,7 @@ enum class PodModel(
hasCase = true,
hasEarDetection = true,
hasAncControl = true,
+ hasMicrophoneMode = true,
hasEarDetectionToggle = true,
hasSleepDetection = true,
),
@@ -444,6 +452,8 @@ enum class PodModel(
hasCase = true,
hasEarDetection = true,
hasAncControl = true,
+ hasMicrophoneMode = true,
+ hasEarDetectionToggle = true,
),
modelNumbers = setOf("A2576", "A2577", "A2578"), // L/R earbuds + case
leftPodIconRes = R.drawable.device_beats_fitpro_left,
@@ -458,6 +468,7 @@ enum class PodModel(
Features(
hasDualPods = true,
hasCase = true,
+ hasEarDetection = true,
),
leftPodIconRes = R.drawable.device_airpods_gen1_left,
rightPodIconRes = R.drawable.device_airpods_gen1_right,
@@ -471,6 +482,7 @@ enum class PodModel(
Features(
hasDualPods = true,
hasCase = true,
+ hasEarDetection = true,
),
leftPodIconRes = R.drawable.device_airpods_gen1_left,
rightPodIconRes = R.drawable.device_airpods_gen1_right,
@@ -484,6 +496,7 @@ enum class PodModel(
Features(
hasDualPods = true,
hasCase = true,
+ hasEarDetection = true,
),
leftPodIconRes = R.drawable.device_airpods_gen3_left,
rightPodIconRes = R.drawable.device_airpods_gen3_right,
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 04bbc366..ddcebaff 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -515,7 +515,7 @@
General
Microphone
- Which AirPod is used as the microphone
+ Which earbud is used as the microphone
Auto
Right
Left
diff --git a/app/src/test/java/eu/darken/capod/pods/core/apple/ModelFeaturesTest.kt b/app/src/test/java/eu/darken/capod/pods/core/apple/ModelFeaturesTest.kt
index accd7772..2db48c97 100644
--- a/app/src/test/java/eu/darken/capod/pods/core/apple/ModelFeaturesTest.kt
+++ b/app/src/test/java/eu/darken/capod/pods/core/apple/ModelFeaturesTest.kt
@@ -1,5 +1,6 @@
package eu.darken.capod.pods.core.apple
+import io.kotest.assertions.withClue
import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Test
import testhelpers.BaseTest
@@ -7,79 +8,364 @@ import testhelpers.BaseTest
class ModelFeaturesTest : BaseTest() {
@Test
- fun `AirPods Pro 3 has all features`() {
- val f = PodModel.AIRPODS_PRO3.features
- f.hasDualPods shouldBe true
- f.hasCase shouldBe true
- f.hasEarDetection shouldBe true
- f.hasAncControl shouldBe true
+ fun `feature flags match supported model matrix`() {
+ featureExpectations.forEach { expectation ->
+ withClue(expectation.name) {
+ modelsWith(expectation.predicate) shouldBe expectation.models
+ }
+ }
}
@Test
- fun `AirPods Gen 1 has dual pods and case but no ear detection or ANC`() {
- val f = PodModel.AIRPODS_GEN1.features
- f.hasDualPods shouldBe true
- f.hasCase shouldBe true
- f.hasEarDetection shouldBe false
- f.hasAncControl shouldBe false
- }
-
- @Test
- fun `AirPods Max is single device with ANC but no dual pods or case`() {
- val f = PodModel.AIRPODS_MAX.features
- f.hasDualPods shouldBe false
- f.hasCase shouldBe false
- f.hasEarDetection shouldBe true
- f.hasAncControl shouldBe true
- }
-
- @Test
- fun `Beats Solo 3 has no features`() {
- val f = PodModel.BEATS_SOLO_3.features
- f.hasDualPods shouldBe false
- f.hasCase shouldBe false
- f.hasEarDetection shouldBe false
- f.hasAncControl shouldBe false
- }
-
- @Test
- fun `PowerBeats Pro has dual pods, case, ear detection but no ANC`() {
- val f = PodModel.POWERBEATS_PRO.features
- f.hasDualPods shouldBe true
- f.hasCase shouldBe true
- f.hasEarDetection shouldBe true
- f.hasAncControl shouldBe false
+ fun `models without ear detection match intentional matrix`() {
+ modelsWithout { it.hasEarDetection } shouldBe setOf(
+ PodModel.BEATS_FLEX,
+ PodModel.BEATS_SOLO_3,
+ PodModel.BEATS_SOLO_PRO,
+ PodModel.BEATS_SOLO_4,
+ PodModel.BEATS_SOLO_BUDS,
+ PodModel.BEATS_STUDIO_3,
+ PodModel.BEATS_STUDIO_BUDS,
+ PodModel.BEATS_STUDIO_BUDS_PLUS,
+ PodModel.BEATS_STUDIO_PRO,
+ PodModel.BEATS_X,
+ PodModel.POWERBEATS_3,
+ PodModel.POWERBEATS_4,
+ PodModel.UNKNOWN,
+ )
}
@Test
fun `UNKNOWN model has no features`() {
- val f = PodModel.UNKNOWN.features
- f.hasDualPods shouldBe false
- f.hasCase shouldBe false
- f.hasEarDetection shouldBe false
- f.hasAncControl shouldBe false
+ PodModel.UNKNOWN.features shouldBe PodModel.Features()
}
@Test
- fun `all ANC-capable dual-pod models have ear detection except Studio Buds`() {
- val noEarDetectionAncModels = setOf(
- PodModel.BEATS_STUDIO_BUDS,
- PodModel.BEATS_STUDIO_BUDS_PLUS,
- )
+ fun `ear detection toggle implies ear detection`() {
PodModel.entries
- .filter { it.features.hasAncControl && it.features.hasDualPods }
- .filter { it !in noEarDetectionAncModels }
+ .filter { it.features.hasEarDetectionToggle }
.forEach { model ->
- model.features.hasEarDetection shouldBe true
+ withClue(model.name) {
+ model.features.hasEarDetection shouldBe true
+ }
}
}
@Test
- fun `all models with case also have dual pods`() {
+ fun `sleep detection implies ear detection`() {
+ PodModel.entries
+ .filter { it.features.hasSleepDetection }
+ .forEach { model ->
+ withClue(model.name) {
+ model.features.hasEarDetection shouldBe true
+ }
+ }
+ }
+
+ @Test
+ fun `case implies dual pods`() {
PodModel.entries
.filter { it.features.hasCase }
.forEach { model ->
- model.features.hasDualPods shouldBe true
+ withClue(model.name) {
+ model.features.hasDualPods shouldBe true
+ }
}
}
-}
\ No newline at end of file
+
+ @Test
+ fun `adaptive ANC implies ANC control`() {
+ PodModel.entries
+ .filter { it.features.hasAdaptiveAnc }
+ .forEach { model ->
+ withClue(model.name) {
+ model.features.hasAncControl shouldBe true
+ }
+ }
+ }
+
+ @Test
+ fun `adaptive audio noise implies adaptive ANC`() {
+ PodModel.entries
+ .filter { it.features.hasAdaptiveAudioNoise }
+ .forEach { model ->
+ withClue(model.name) {
+ model.features.hasAdaptiveAnc shouldBe true
+ }
+ }
+ }
+
+ @Test
+ fun `listening mode cycle implies ANC control`() {
+ PodModel.entries
+ .filter { it.features.hasListeningModeCycle }
+ .forEach { model ->
+ withClue(model.name) {
+ model.features.hasAncControl shouldBe true
+ }
+ }
+ }
+
+ @Test
+ fun `allow off option implies listening mode cycle`() {
+ PodModel.entries
+ .filter { it.features.hasAllowOffOption }
+ .forEach { model ->
+ withClue(model.name) {
+ model.features.hasListeningModeCycle shouldBe true
+ }
+ }
+ }
+
+ private fun modelsWith(predicate: (PodModel.Features) -> Boolean): Set = PodModel.entries
+ .filter { predicate(it.features) }
+ .toSet()
+
+ private fun modelsWithout(predicate: (PodModel.Features) -> Boolean): Set = PodModel.entries
+ .filterNot { predicate(it.features) }
+ .toSet()
+
+ private data class FeatureExpectation(
+ val name: String,
+ val predicate: (PodModel.Features) -> Boolean,
+ val models: Set,
+ )
+
+ private fun feature(
+ name: String,
+ predicate: (PodModel.Features) -> Boolean,
+ models: Set,
+ ) = FeatureExpectation(name, predicate, models)
+
+ private val dualPodModels = setOf(
+ PodModel.AIRPODS_GEN1,
+ PodModel.AIRPODS_GEN2,
+ PodModel.AIRPODS_GEN3,
+ PodModel.AIRPODS_GEN4,
+ PodModel.AIRPODS_GEN4_ANC,
+ PodModel.AIRPODS_PRO,
+ PodModel.AIRPODS_PRO2,
+ PodModel.AIRPODS_PRO2_USBC,
+ PodModel.AIRPODS_PRO3,
+ PodModel.BEATS_SOLO_BUDS,
+ PodModel.BEATS_STUDIO_BUDS,
+ PodModel.BEATS_STUDIO_BUDS_PLUS,
+ PodModel.POWERBEATS_PRO,
+ PodModel.POWERBEATS_PRO2,
+ PodModel.BEATS_FIT_PRO,
+ PodModel.FAKE_AIRPODS_GEN1,
+ PodModel.FAKE_AIRPODS_GEN2,
+ PodModel.FAKE_AIRPODS_GEN3,
+ PodModel.FAKE_AIRPODS_PRO,
+ PodModel.FAKE_AIRPODS_PRO2,
+ )
+
+ private val earDetectionModels = setOf(
+ PodModel.AIRPODS_GEN1,
+ PodModel.AIRPODS_GEN2,
+ PodModel.AIRPODS_GEN3,
+ PodModel.AIRPODS_GEN4,
+ PodModel.AIRPODS_GEN4_ANC,
+ PodModel.AIRPODS_PRO,
+ PodModel.AIRPODS_PRO2,
+ PodModel.AIRPODS_PRO2_USBC,
+ PodModel.AIRPODS_PRO3,
+ PodModel.AIRPODS_MAX,
+ PodModel.AIRPODS_MAX_USBC,
+ PodModel.AIRPODS_MAX2,
+ PodModel.POWERBEATS_PRO,
+ PodModel.POWERBEATS_PRO2,
+ PodModel.BEATS_FIT_PRO,
+ PodModel.FAKE_AIRPODS_GEN1,
+ PodModel.FAKE_AIRPODS_GEN2,
+ PodModel.FAKE_AIRPODS_GEN3,
+ PodModel.FAKE_AIRPODS_PRO,
+ PodModel.FAKE_AIRPODS_PRO2,
+ )
+
+ private val ancControlModels = setOf(
+ PodModel.AIRPODS_GEN4_ANC,
+ PodModel.AIRPODS_PRO,
+ PodModel.AIRPODS_PRO2,
+ PodModel.AIRPODS_PRO2_USBC,
+ PodModel.AIRPODS_PRO3,
+ PodModel.AIRPODS_MAX,
+ PodModel.AIRPODS_MAX_USBC,
+ PodModel.AIRPODS_MAX2,
+ PodModel.BEATS_SOLO_PRO,
+ PodModel.BEATS_STUDIO_3,
+ PodModel.BEATS_STUDIO_BUDS,
+ PodModel.BEATS_STUDIO_BUDS_PLUS,
+ PodModel.BEATS_STUDIO_PRO,
+ PodModel.POWERBEATS_PRO2,
+ PodModel.BEATS_FIT_PRO,
+ PodModel.FAKE_AIRPODS_PRO,
+ PodModel.FAKE_AIRPODS_PRO2,
+ )
+
+ private val adaptiveAncModels = setOf(
+ PodModel.AIRPODS_GEN4_ANC,
+ PodModel.AIRPODS_PRO2,
+ PodModel.AIRPODS_PRO2_USBC,
+ PodModel.AIRPODS_PRO3,
+ PodModel.AIRPODS_MAX2,
+ )
+
+ private val conversationAwarenessModels = setOf(
+ PodModel.AIRPODS_GEN4_ANC,
+ PodModel.AIRPODS_PRO2,
+ PodModel.AIRPODS_PRO2_USBC,
+ PodModel.AIRPODS_PRO3,
+ PodModel.AIRPODS_MAX2,
+ )
+
+ private val ncOneAirpodModels = setOf(
+ PodModel.AIRPODS_GEN4_ANC,
+ PodModel.AIRPODS_PRO,
+ PodModel.AIRPODS_PRO2,
+ PodModel.AIRPODS_PRO2_USBC,
+ PodModel.AIRPODS_PRO3,
+ )
+
+ private val pressSpeedModels = setOf(
+ PodModel.AIRPODS_GEN3,
+ PodModel.AIRPODS_GEN4,
+ PodModel.AIRPODS_GEN4_ANC,
+ PodModel.AIRPODS_PRO,
+ PodModel.AIRPODS_PRO2,
+ PodModel.AIRPODS_PRO2_USBC,
+ PodModel.AIRPODS_PRO3,
+ PodModel.AIRPODS_MAX,
+ PodModel.AIRPODS_MAX_USBC,
+ PodModel.AIRPODS_MAX2,
+ )
+
+ private val volumeSwipeModels = setOf(
+ PodModel.AIRPODS_PRO2,
+ PodModel.AIRPODS_PRO2_USBC,
+ PodModel.AIRPODS_PRO3,
+ )
+
+ private val personalizedVolumeModels = setOf(
+ PodModel.AIRPODS_GEN4_ANC,
+ PodModel.AIRPODS_PRO2,
+ PodModel.AIRPODS_PRO2_USBC,
+ PodModel.AIRPODS_PRO3,
+ PodModel.AIRPODS_MAX2,
+ )
+
+ private val toneVolumeModels = setOf(
+ PodModel.AIRPODS_GEN3,
+ PodModel.AIRPODS_GEN4,
+ PodModel.AIRPODS_GEN4_ANC,
+ PodModel.AIRPODS_PRO,
+ PodModel.AIRPODS_PRO2,
+ PodModel.AIRPODS_PRO2_USBC,
+ PodModel.AIRPODS_PRO3,
+ PodModel.AIRPODS_MAX,
+ PodModel.AIRPODS_MAX_USBC,
+ PodModel.AIRPODS_MAX2,
+ )
+
+ private val endCallMuteMicModels = setOf(
+ PodModel.AIRPODS_GEN3,
+ PodModel.AIRPODS_GEN4,
+ PodModel.AIRPODS_GEN4_ANC,
+ PodModel.AIRPODS_PRO,
+ PodModel.AIRPODS_PRO2,
+ PodModel.AIRPODS_PRO2_USBC,
+ PodModel.AIRPODS_PRO3,
+ )
+
+ private val adaptiveAudioNoiseModels = setOf(
+ PodModel.AIRPODS_GEN4_ANC,
+ PodModel.AIRPODS_PRO2,
+ PodModel.AIRPODS_PRO2_USBC,
+ PodModel.AIRPODS_PRO3,
+ PodModel.AIRPODS_MAX2,
+ )
+
+ private val microphoneModeModels = setOf(
+ PodModel.AIRPODS_GEN1,
+ PodModel.AIRPODS_GEN2,
+ PodModel.AIRPODS_GEN3,
+ PodModel.AIRPODS_GEN4,
+ PodModel.AIRPODS_GEN4_ANC,
+ PodModel.AIRPODS_PRO,
+ PodModel.AIRPODS_PRO2,
+ PodModel.AIRPODS_PRO2_USBC,
+ PodModel.AIRPODS_PRO3,
+ PodModel.POWERBEATS_PRO2,
+ PodModel.BEATS_FIT_PRO,
+ )
+
+ private val earDetectionToggleModels = setOf(
+ PodModel.AIRPODS_GEN1,
+ PodModel.AIRPODS_GEN2,
+ PodModel.AIRPODS_GEN3,
+ PodModel.AIRPODS_GEN4,
+ PodModel.AIRPODS_GEN4_ANC,
+ PodModel.AIRPODS_PRO,
+ PodModel.AIRPODS_PRO2,
+ PodModel.AIRPODS_PRO2_USBC,
+ PodModel.AIRPODS_PRO3,
+ PodModel.AIRPODS_MAX,
+ PodModel.AIRPODS_MAX_USBC,
+ PodModel.AIRPODS_MAX2,
+ PodModel.POWERBEATS_PRO,
+ PodModel.POWERBEATS_PRO2,
+ PodModel.BEATS_FIT_PRO,
+ )
+
+ private val listeningModeCycleModels = setOf(
+ PodModel.AIRPODS_GEN4_ANC,
+ PodModel.AIRPODS_PRO,
+ PodModel.AIRPODS_PRO2,
+ PodModel.AIRPODS_PRO2_USBC,
+ PodModel.AIRPODS_PRO3,
+ PodModel.AIRPODS_MAX,
+ PodModel.AIRPODS_MAX_USBC,
+ PodModel.AIRPODS_MAX2,
+ )
+
+ private val stemConfigModels = setOf(
+ PodModel.AIRPODS_GEN4_ANC,
+ PodModel.AIRPODS_PRO2,
+ PodModel.AIRPODS_PRO2_USBC,
+ PodModel.AIRPODS_PRO3,
+ )
+
+ private val sleepDetectionModels = setOf(
+ PodModel.AIRPODS_GEN4,
+ PodModel.AIRPODS_GEN4_ANC,
+ PodModel.AIRPODS_PRO2,
+ PodModel.AIRPODS_PRO2_USBC,
+ PodModel.AIRPODS_PRO3,
+ PodModel.POWERBEATS_PRO2,
+ )
+
+ private val featureExpectations = listOf(
+ feature("hasDualPods", { it.hasDualPods }, dualPodModels),
+ feature("hasCase", { it.hasCase }, dualPodModels),
+ feature("hasEarDetection", { it.hasEarDetection }, earDetectionModels),
+ feature("hasAncControl", { it.hasAncControl }, ancControlModels),
+ feature("hasAdaptiveAnc", { it.hasAdaptiveAnc }, adaptiveAncModels),
+ feature("hasConversationAwareness", { it.hasConversationAwareness }, conversationAwarenessModels),
+ feature("hasNcOneAirpod", { it.hasNcOneAirpod }, ncOneAirpodModels),
+ feature("hasPressSpeed", { it.hasPressSpeed }, pressSpeedModels),
+ feature("hasPressHoldDuration", { it.hasPressHoldDuration }, pressSpeedModels),
+ feature("hasVolumeSwipe", { it.hasVolumeSwipe }, volumeSwipeModels),
+ feature("hasVolumeSwipeLength", { it.hasVolumeSwipeLength }, volumeSwipeModels),
+ feature("hasPersonalizedVolume", { it.hasPersonalizedVolume }, personalizedVolumeModels),
+ feature("hasToneVolume", { it.hasToneVolume }, toneVolumeModels),
+ feature("hasEndCallMuteMic", { it.hasEndCallMuteMic }, endCallMuteMicModels),
+ feature("hasAdaptiveAudioNoise", { it.hasAdaptiveAudioNoise }, adaptiveAudioNoiseModels),
+ feature("hasMicrophoneMode", { it.hasMicrophoneMode }, microphoneModeModels),
+ feature("hasEarDetectionToggle", { it.hasEarDetectionToggle }, earDetectionToggleModels),
+ feature("hasListeningModeCycle", { it.hasListeningModeCycle }, listeningModeCycleModels),
+ feature("hasAllowOffOption", { it.hasAllowOffOption }, listeningModeCycleModels),
+ feature("hasStemConfig", { it.hasStemConfig }, stemConfigModels),
+ feature("hasSleepDetection", { it.hasSleepDetection }, sleepDetectionModels),
+ feature("hasDynamicEndOfCharge", { it.hasDynamicEndOfCharge }, setOf(PodModel.AIRPODS_PRO3)),
+ )
+}
diff --git a/app/src/test/java/eu/darken/capod/pods/core/apple/aap/devices/DefaultAapDeviceProfileNewSettingsTest.kt b/app/src/test/java/eu/darken/capod/pods/core/apple/aap/devices/DefaultAapDeviceProfileNewSettingsTest.kt
index fb2596d9..df27cb5b 100644
--- a/app/src/test/java/eu/darken/capod/pods/core/apple/aap/devices/DefaultAapDeviceProfileNewSettingsTest.kt
+++ b/app/src/test/java/eu/darken/capod/pods/core/apple/aap/devices/DefaultAapDeviceProfileNewSettingsTest.kt
@@ -337,6 +337,7 @@ class DefaultAapDeviceProfileNewSettingsTest : BaseAapSessionTest() {
f.hasPressSpeed shouldBe true
f.hasPressHoldDuration shouldBe true
f.hasToneVolume shouldBe true
+ f.hasEndCallMuteMic shouldBe true
f.hasListeningModeCycle shouldBe false
f.hasStemConfig shouldBe false
f.hasSleepDetection shouldBe true
@@ -351,14 +352,31 @@ class DefaultAapDeviceProfileNewSettingsTest : BaseAapSessionTest() {
f.hasStemConfig shouldBe false
}
- @Test fun `Gen 1 has mic mode only`() {
+ @Test fun `Gen 1 has mic mode and ear detection toggle`() {
val f = PodModel.AIRPODS_GEN1.features
f.hasMicrophoneMode shouldBe true
- f.hasEarDetectionToggle shouldBe false
+ f.hasEarDetection shouldBe true
+ f.hasEarDetectionToggle shouldBe true
f.hasListeningModeCycle shouldBe false
f.hasStemConfig shouldBe false
}
+ @Test fun `Gen 2 and Gen 3 expose ear detection toggle`() {
+ val gen2 = PodModel.AIRPODS_GEN2.features
+ gen2.hasMicrophoneMode shouldBe true
+ gen2.hasEarDetection shouldBe true
+ gen2.hasEarDetectionToggle shouldBe true
+
+ val gen3 = PodModel.AIRPODS_GEN3.features
+ gen3.hasMicrophoneMode shouldBe true
+ gen3.hasEarDetection shouldBe true
+ gen3.hasEarDetectionToggle shouldBe true
+ gen3.hasPressSpeed shouldBe true
+ gen3.hasPressHoldDuration shouldBe true
+ gen3.hasToneVolume shouldBe true
+ gen3.hasEndCallMuteMic shouldBe true
+ }
+
@Test fun `Max 2 has H2 features`() {
val f = PodModel.AIRPODS_MAX2.features
f.hasAdaptiveAnc shouldBe true
@@ -382,6 +400,18 @@ class DefaultAapDeviceProfileNewSettingsTest : BaseAapSessionTest() {
f.hasEarDetectionToggle shouldBe true
f.hasAncControl shouldBe true
f.hasEarDetection shouldBe true
+ f.hasMicrophoneMode shouldBe true
+ }
+
+ @Test fun `Powerbeats Pro and Beats Fit Pro expose ear detection toggle`() {
+ val powerbeatsPro = PodModel.POWERBEATS_PRO.features
+ powerbeatsPro.hasEarDetection shouldBe true
+ powerbeatsPro.hasEarDetectionToggle shouldBe true
+
+ val beatsFitPro = PodModel.BEATS_FIT_PRO.features
+ beatsFitPro.hasEarDetection shouldBe true
+ beatsFitPro.hasEarDetectionToggle shouldBe true
+ beatsFitPro.hasMicrophoneMode shouldBe true
}
}
@@ -413,6 +443,14 @@ class DefaultAapDeviceProfileNewSettingsTest : BaseAapSessionTest() {
}
}
+ @Test fun `earDetectionToggle implies earDetection`() {
+ for (model in PodModel.entries) {
+ if (model.features.hasEarDetectionToggle) {
+ model.features.hasEarDetection shouldBe true
+ }
+ }
+ }
+
@Test fun `adaptiveAudioNoise implies adaptiveAnc`() {
for (model in PodModel.entries) {
if (model.features.hasAdaptiveAudioNoise) {