Skip to content

Commit 6b922e8

Browse files
committed
Fix AirPods Max and AirPods Max (USB-C) support
Closes #236
1 parent 663e2d3 commit 6b922e8

File tree

5 files changed

+44
-12
lines changed

5 files changed

+44
-12
lines changed

app-common/src/main/java/eu/darken/capod/pods/core/PodDevice.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@ interface PodDevice {
107107
"AirPods Max",
108108
R.drawable.devic_headphones_generic
109109
),
110-
@Json(name = "airpods.max2") AIRPODS_MAX2(
111-
"AirPods Max 2",
110+
@Json(name = "airpods.max.usbc") AIRPODS_MAX_USBC(
111+
"AirPods Max USB-C",
112112
R.drawable.devic_headphones_generic
113113
),
114114
@Json(name = "beats.flex") BEATS_FLEX(

app-common/src/main/java/eu/darken/capod/pods/core/apple/AppleFactoryModule.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import eu.darken.capod.pods.core.apple.airpods.AirPodsGen3
1111
import eu.darken.capod.pods.core.apple.airpods.AirPodsGen4
1212
import eu.darken.capod.pods.core.apple.airpods.AirPodsGen4Anc
1313
import eu.darken.capod.pods.core.apple.airpods.AirPodsMax
14-
import eu.darken.capod.pods.core.apple.airpods.AirPodsMax2
14+
import eu.darken.capod.pods.core.apple.airpods.AirPodsMaxUsbc
1515
import eu.darken.capod.pods.core.apple.airpods.AirPodsPro
1616
import eu.darken.capod.pods.core.apple.airpods.AirPodsPro2
1717
import eu.darken.capod.pods.core.apple.airpods.AirPodsPro2Usbc
@@ -43,7 +43,7 @@ abstract class AppleFactoryModule {
4343
@Binds @IntoSet abstract fun airPodsPro2(factory: AirPodsPro2.Factory): ApplePodsFactory<out ApplePods>
4444
@Binds @IntoSet abstract fun airPodsPro2Usbc(factory: AirPodsPro2Usbc.Factory): ApplePodsFactory<out ApplePods>
4545
@Binds @IntoSet abstract fun airPodsMax(factory: AirPodsMax.Factory): ApplePodsFactory<out ApplePods>
46-
@Binds @IntoSet abstract fun airPodsMax2(factory: AirPodsMax2.Factory): ApplePodsFactory<out ApplePods>
46+
@Binds @IntoSet abstract fun airPodsMax2(factory: AirPodsMaxUsbc.Factory): ApplePodsFactory<out ApplePods>
4747

4848
@Binds @IntoSet abstract fun beatsFlex(factory: BeatsFlex.Factory): ApplePodsFactory<out ApplePods>
4949
@Binds @IntoSet abstract fun beatsSolo3(factory: BeatsSolo3.Factory): ApplePodsFactory<out ApplePods>

app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMax.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ data class AirPodsMax(
3939
class Factory @Inject constructor() : SingleApplePodsFactory(TAG) {
4040

4141
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
42-
getModelInfo().dirty == DEVICE_CODE_DIRTY && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
42+
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
4343
}
4444

4545
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
@@ -63,7 +63,7 @@ data class AirPodsMax(
6363
}
6464

6565
companion object {
66-
private val DEVICE_CODE_DIRTY = 0x200A.toUByte()
66+
private val DEVICE_CODE = 0x0A20.toUShort()
6767
private val TAG = logTag("PodDevice", "Apple", "AirPods", "Max")
6868
}
6969
}

app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMax2.kt app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMaxUsbc.kt

+6-6
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
1414
import java.time.Instant
1515
import javax.inject.Inject
1616

17-
data class AirPodsMax2(
17+
data class AirPodsMaxUsbc(
1818
override val identifier: PodDevice.Id = PodDevice.Id(),
1919
override val seenLastAt: Instant = Instant.now(),
2020
override val seenFirstAt: Instant = Instant.now(),
@@ -25,7 +25,7 @@ data class AirPodsMax2(
2525
private val rssiAverage: Int? = null,
2626
) : SingleApplePods, HasEarDetection, HasChargeDetection, HasAppleColor {
2727

28-
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_MAX2
28+
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_MAX_USBC
2929

3030
override val rssi: Int
3131
get() = rssiAverage ?: super<SingleApplePods>.rssi
@@ -39,11 +39,11 @@ data class AirPodsMax2(
3939
class Factory @Inject constructor() : SingleApplePodsFactory(TAG) {
4040

4141
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
42-
getModelInfo().dirty == DEVICE_CODE_DIRTY && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
42+
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
4343
}
4444

4545
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
46-
var basic = AirPodsMax2(scanResult = scanResult, proximityMessage = message)
46+
var basic = AirPodsMaxUsbc(scanResult = scanResult, proximityMessage = message)
4747
val result = searchHistory(basic)
4848

4949
if (result != null) basic = basic.copy(identifier = result.id)
@@ -63,7 +63,7 @@ data class AirPodsMax2(
6363
}
6464

6565
companion object {
66-
private val DEVICE_CODE_DIRTY = 0x201F.toUByte()
67-
private val TAG = logTag("PodDevice", "Apple", "AirPods", "Max2")
66+
private val DEVICE_CODE = 0x1F20.toUShort()
67+
private val TAG = logTag("PodDevice", "Apple", "AirPods", "Max", "USBC")
6868
}
6969
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package eu.darken.capod.pods.core.apple.airpods
2+
3+
import eu.darken.capod.pods.core.PodDevice
4+
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
5+
import io.kotest.matchers.shouldBe
6+
import kotlinx.coroutines.test.runTest
7+
import org.junit.jupiter.api.Test
8+
9+
class AirPodsMaxUsbcTest : BaseAirPodsTest() {
10+
11+
// Test data from https://github.com/d4rken-org/capod/issues/236
12+
@Test
13+
fun `default AirPods Max`() = runTest {
14+
create<AirPodsMaxUsbc>("07 19 01 1F 20 2B 05 80 03 12 C5 2E 8B F9 9A 7E 19 7B 63 0F 30 6E D7 3B E2 EC 32") {
15+
rawPrefix shouldBe 0x01.toUByte()
16+
rawDeviceModel shouldBe 0x1F20.toUShort()
17+
rawStatus shouldBe 0x2B.toUByte()
18+
rawPodsBattery shouldBe 0x05.toUByte()
19+
rawFlags shouldBe 0x8.toUShort()
20+
rawCaseBattery shouldBe 0x0.toUShort()
21+
rawCaseLidState shouldBe 0x03.toUByte()
22+
rawDeviceColor shouldBe 0x12.toUByte()
23+
rawSuffix shouldBe 0xC5.toUByte()
24+
25+
batteryHeadsetPercent shouldBe 0.5f
26+
27+
isHeadsetBeingCharged shouldBe false
28+
29+
model shouldBe PodDevice.Model.AIRPODS_MAX_USBC
30+
}
31+
}
32+
}

0 commit comments

Comments
 (0)