Compare commits

...
9 changed files with 114 additions and 4 deletions
+1 -1
View File
@@ -1 +1 @@
2.15.0-rc0 21500000
2.16.0-rc0 21600000
@@ -107,6 +107,10 @@ interface PodDevice {
"AirPods Max",
R.drawable.devic_headphones_generic
),
@Json(name = "airpods.max.usbc") AIRPODS_MAX_USBC(
"AirPods Max USB-C",
R.drawable.devic_headphones_generic
),
@Json(name = "beats.flex") BEATS_FLEX(
"Beats Flex"
),
@@ -11,6 +11,7 @@ import eu.darken.capod.pods.core.apple.airpods.AirPodsGen3
import eu.darken.capod.pods.core.apple.airpods.AirPodsGen4
import eu.darken.capod.pods.core.apple.airpods.AirPodsGen4Anc
import eu.darken.capod.pods.core.apple.airpods.AirPodsMax
import eu.darken.capod.pods.core.apple.airpods.AirPodsMaxUsbc
import eu.darken.capod.pods.core.apple.airpods.AirPodsPro
import eu.darken.capod.pods.core.apple.airpods.AirPodsPro2
import eu.darken.capod.pods.core.apple.airpods.AirPodsPro2Usbc
@@ -42,6 +43,7 @@ abstract class AppleFactoryModule {
@Binds @IntoSet abstract fun airPodsPro2(factory: AirPodsPro2.Factory): ApplePodsFactory<out ApplePods>
@Binds @IntoSet abstract fun airPodsPro2Usbc(factory: AirPodsPro2Usbc.Factory): ApplePodsFactory<out ApplePods>
@Binds @IntoSet abstract fun airPodsMax(factory: AirPodsMax.Factory): ApplePodsFactory<out ApplePods>
@Binds @IntoSet abstract fun airPodsMax2(factory: AirPodsMaxUsbc.Factory): ApplePodsFactory<out ApplePods>
@Binds @IntoSet abstract fun beatsFlex(factory: BeatsFlex.Factory): ApplePodsFactory<out ApplePods>
@Binds @IntoSet abstract fun beatsSolo3(factory: BeatsSolo3.Factory): ApplePodsFactory<out ApplePods>
@@ -39,7 +39,7 @@ data class AirPodsMax(
class Factory @Inject constructor() : SingleApplePodsFactory(TAG) {
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
getModelInfo().dirty == DEVICE_CODE_DIRTY && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
}
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
@@ -63,7 +63,7 @@ data class AirPodsMax(
}
companion object {
private val DEVICE_CODE_DIRTY = 10.toUByte()
private val DEVICE_CODE = 0x0A20.toUShort()
private val TAG = logTag("PodDevice", "Apple", "AirPods", "Max")
}
}
@@ -0,0 +1,69 @@
package eu.darken.capod.pods.core.apple.airpods
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.isBitSet
import eu.darken.capod.pods.core.HasChargeDetection
import eu.darken.capod.pods.core.HasEarDetection
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.apple.HasAppleColor
import eu.darken.capod.pods.core.apple.SingleApplePods
import eu.darken.capod.pods.core.apple.SingleApplePodsFactory
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
import java.time.Instant
import javax.inject.Inject
data class AirPodsMaxUsbc(
override val identifier: PodDevice.Id = PodDevice.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val proximityMessage: ProximityPairing.Message,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
) : SingleApplePods, HasEarDetection, HasChargeDetection, HasAppleColor {
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_MAX_USBC
override val rssi: Int
get() = rssiAverage ?: super<SingleApplePods>.rssi
override val isHeadsetBeingCharged: Boolean
get() = rawFlags.isBitSet(0)
override val isBeingWorn: Boolean
get() = rawStatus.isBitSet(5)
class Factory @Inject constructor() : SingleApplePodsFactory(TAG) {
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
}
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
var basic = AirPodsMaxUsbc(scanResult = scanResult, proximityMessage = message)
val result = searchHistory(basic)
if (result != null) basic = basic.copy(identifier = result.id)
updateHistory(basic)
if (result == null) return basic
return basic.copy(
identifier = result.id,
seenFirstAt = result.seenFirstAt,
seenLastAt = scanResult.receivedAt,
seenCounter = result.seenCounter,
reliability = result.reliability,
rssiAverage = result.rssiSmoothed(basic.rssi),
)
}
}
companion object {
private val DEVICE_CODE = 0x1F20.toUShort()
private val TAG = logTag("PodDevice", "Apple", "AirPods", "Max", "USBC")
}
}
@@ -0,0 +1,32 @@
package eu.darken.capod.pods.core.apple.airpods
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
class AirPodsMaxUsbcTest : BaseAirPodsTest() {
// Test data from https://github.com/d4rken-org/capod/issues/236
@Test
fun `default AirPods Max`() = runTest {
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") {
rawPrefix shouldBe 0x01.toUByte()
rawDeviceModel shouldBe 0x1F20.toUShort()
rawStatus shouldBe 0x2B.toUByte()
rawPodsBattery shouldBe 0x05.toUByte()
rawFlags shouldBe 0x8.toUShort()
rawCaseBattery shouldBe 0x0.toUShort()
rawCaseLidState shouldBe 0x03.toUByte()
rawDeviceColor shouldBe 0x12.toUByte()
rawSuffix shouldBe 0xC5.toUByte()
batteryHeadsetPercent shouldBe 0.5f
isHeadsetBeingCharged shouldBe false
model shouldBe PodDevice.Model.AIRPODS_MAX_USBC
}
}
}
@@ -84,6 +84,7 @@
android:layout_height="wrap_content"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="32dp"
android:layout_marginBottom="64dp"
android:text="@string/general_continue_action"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@@ -0,0 +1,2 @@
Bugfixes, performance improvements and maybe new features.
¯\_(ツ)_/¯
+1 -1
View File
@@ -1,6 +1,6 @@
### Updated by release.sh ###
project.versioning.major=2
project.versioning.minor=15
project.versioning.minor=16
project.versioning.patch=0
project.versioning.build=0
#############################