From 3b00613ddeffdeff5dd6352480c43fa5975df2af Mon Sep 17 00:00:00 2001 From: darken Date: Wed, 4 Jun 2025 15:48:13 +0200 Subject: [PATCH] Refactor proximity message handling, introduce "Payload" that splits the data in public and private part. --- .../darken/capod/monitor/core/PodMonitor.kt | 3 +- .../darken/capod/monitor/core/RPAChecker.kt | 7 +- .../capod/pods/core/apple/AppleFactory.kt | 41 +++++-- .../darken/capod/pods/core/apple/ApplePods.kt | 52 ++++---- .../capod/pods/core/apple/ApplePodsFactory.kt | 38 +++--- .../capod/pods/core/apple/DualApplePods.kt | 58 ++++----- .../pods/core/apple/DualApplePodsFactory.kt | 2 +- .../capod/pods/core/apple/HasAppleColor.kt | 4 +- .../capod/pods/core/apple/SingleApplePods.kt | 2 +- .../pods/core/apple/airpods/AirPodsGen1.kt | 13 +- .../pods/core/apple/airpods/AirPodsGen2.kt | 13 +- .../pods/core/apple/airpods/AirPodsGen3.kt | 13 +- .../pods/core/apple/airpods/AirPodsGen4.kt | 11 +- .../pods/core/apple/airpods/AirPodsGen4Anc.kt | 11 +- .../pods/core/apple/airpods/AirPodsMax.kt | 15 +-- .../pods/core/apple/airpods/AirPodsMaxUsbc.kt | 15 +-- .../pods/core/apple/airpods/AirPodsPro.kt | 13 +- .../pods/core/apple/airpods/AirPodsPro2.kt | 11 +- .../core/apple/airpods/AirPodsPro2Usbc.kt | 11 +- .../apple/airpods/HasStateDetectionAirPods.kt | 2 +- .../pods/core/apple/beats/BeatsFitPro.kt | 11 +- .../capod/pods/core/apple/beats/BeatsFlex.kt | 11 +- .../capod/pods/core/apple/beats/BeatsSolo3.kt | 11 +- .../pods/core/apple/beats/BeatsStudio3.kt | 11 +- .../capod/pods/core/apple/beats/BeatsX.kt | 11 +- .../pods/core/apple/beats/PowerBeats3.kt | 11 +- .../pods/core/apple/beats/PowerBeats4.kt | 11 +- .../pods/core/apple/beats/PowerBeatsPro.kt | 11 +- .../pods/core/apple/misc/FakeAirPodsGen1.kt | 111 +++--------------- .../pods/core/apple/misc/FakeAirPodsGen2.kt | 109 +++-------------- .../pods/core/apple/misc/FakeAirPodsGen3.kt | 109 +++-------------- .../pods/core/apple/misc/FakeAirPodsPro.kt | 109 +++-------------- .../pods/core/apple/misc/FakeAirPodsPro2.kt | 110 +++-------------- .../core/apple/misc/UnknownAppleDevice.kt | 12 +- .../core/apple/protocol/MessageDecrypter.kt | 44 ------- .../core/apple/protocol/ProximityMessage.kt | 58 +++++++++ .../core/apple/protocol/ProximityPairing.kt | 16 +-- .../core/apple/protocol/ProximityPayload.kt | 15 +++ .../reaction/core/popup/PopUpReaction.kt | 4 +- .../capod/pods/core/apple/BaseAirPodsTest.kt | 20 +++- .../core/apple/BasicSingleApplePodsTest.kt | 18 +-- .../pods/core/apple/DualApplePodsTest.kt | 18 +-- .../pods/core/apple/SingleApplePodsTest.kt | 18 +-- .../core/apple/airpods/AirPodsGen1Test.kt | 18 +-- .../core/apple/airpods/AirPodsGen2Test.kt | 18 +-- .../core/apple/airpods/AirPodsGen3Test.kt | 18 +-- .../core/apple/airpods/AirPodsGen4AncTest.kt | 18 +-- .../core/apple/airpods/AirPodsGen4Test.kt | 18 +-- .../pods/core/apple/airpods/AirPodsMaxTest.kt | 64 +++++----- .../core/apple/airpods/AirPodsMaxUsbcTest.kt | 18 +-- .../core/apple/airpods/AirPodsPro2Test.kt | 36 +++--- .../core/apple/airpods/AirPodsPro2UsbcTest.kt | 18 +-- .../pods/core/apple/airpods/AirPodsProTest.kt | 20 ++-- .../pods/core/apple/beats/BeatsFitProTest.kt | 36 +++--- .../pods/core/apple/beats/BeatsFlexText.kt | 18 +-- .../pods/core/apple/beats/BeatsSolo3Test.kt | 18 +-- .../pods/core/apple/beats/BeatsStudio3Test.kt | 18 +-- .../capod/pods/core/apple/beats/BeatsXTest.kt | 18 +-- .../pods/core/apple/beats/PowerBeats3Test.kt | 18 +-- .../pods/core/apple/beats/PowerBeats4Test.kt | 54 ++++----- .../core/apple/beats/PowerBeatsProTest.kt | 18 +-- .../core/apple/misc/FakeAirPodsGen1Test.kt | 18 +-- .../core/apple/misc/FakeAirPodsGen2Test.kt | 18 +-- .../core/apple/misc/FakeAirPodsGen3Test.kt | 18 +-- .../core/apple/misc/FakeAirPodsPro2Test.kt | 36 +++--- .../core/apple/misc/FakeAirPodsProTest.kt | 18 +-- 66 files changed, 706 insertions(+), 1040 deletions(-) delete mode 100644 app-common/src/main/java/eu/darken/capod/pods/core/apple/protocol/MessageDecrypter.kt create mode 100644 app-common/src/main/java/eu/darken/capod/pods/core/apple/protocol/ProximityMessage.kt create mode 100644 app-common/src/main/java/eu/darken/capod/pods/core/apple/protocol/ProximityPayload.kt diff --git a/app-common/src/main/java/eu/darken/capod/monitor/core/PodMonitor.kt b/app-common/src/main/java/eu/darken/capod/monitor/core/PodMonitor.kt index c3792428..87f598a3 100644 --- a/app-common/src/main/java/eu/darken/capod/monitor/core/PodMonitor.kt +++ b/app-common/src/main/java/eu/darken/capod/monitor/core/PodMonitor.kt @@ -19,7 +19,6 @@ import eu.darken.capod.main.core.GeneralSettings import eu.darken.capod.main.core.PermissionTool import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.PodFactory -import eu.darken.capod.monitor.core.RPAChecker import eu.darken.capod.pods.core.apple.protocol.ProximityPairing import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay @@ -202,7 +201,7 @@ class PodMonitor @Inject constructor( private fun determineMainDevice(pods: List): PodDevice? { generalSettings.mainDeviceIdentityKey.value .also { log(TAG) { "Identity-Resolving-Key (IRK): ${it?.toByteString()}" } } - ?.let { irkKey -> pods.firstOrNull { pod -> rpaChecker.verify(pod, irkKey) } } + ?.let { irkKey -> pods.firstOrNull { pod -> rpaChecker.verify(pod.address, irkKey) } } ?.let { log(TAG) { "Main device determined via IRK: $it" } return it diff --git a/app-common/src/main/java/eu/darken/capod/monitor/core/RPAChecker.kt b/app-common/src/main/java/eu/darken/capod/monitor/core/RPAChecker.kt index 4db04bc4..343bc08b 100644 --- a/app-common/src/main/java/eu/darken/capod/monitor/core/RPAChecker.kt +++ b/app-common/src/main/java/eu/darken/capod/monitor/core/RPAChecker.kt @@ -5,7 +5,6 @@ import eu.darken.capod.common.debug.logging.Logging.Priority.ERROR import eu.darken.capod.common.debug.logging.asLog import eu.darken.capod.common.debug.logging.log import eu.darken.capod.common.debug.logging.logTag -import eu.darken.capod.pods.core.PodDevice import okio.ByteString.Companion.toByteString import javax.crypto.Cipher import javax.crypto.spec.SecretKeySpec @@ -14,14 +13,14 @@ import javax.inject.Inject class RPAChecker @Inject constructor() { // Resolvable-Private-Address - fun verify(dev: PodDevice, irk: ByteArray): Boolean = try { - val rpa = dev.address.split(":").map { it.toInt(16).toByte() }.reversed().toByteArray() + fun verify(address: String, irk: ByteArray): Boolean = try { + val rpa = address.split(":").map { it.toInt(16).toByte() }.reversed().toByteArray() val prand = rpa.copyOfRange(3, 6) val hash = rpa.copyOfRange(0, 3) val computedHash = ah(irk, prand) hash.contentEquals(computedHash) } catch (e: Exception) { - log(TAG, ERROR) { "Failed to verify RPA\nPod=${dev}\nIRK=${irk.toByteString()}\n${e.asLog()}" } + log(TAG, ERROR) { "Failed to verify RPA\naddress=${address}\nIRK=${irk.toByteString()}\n${e.asLog()}" } false } diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/AppleFactory.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/AppleFactory.kt index 3751644c..b6d4637c 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/AppleFactory.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/AppleFactory.kt @@ -6,11 +6,13 @@ import eu.darken.capod.common.debug.logging.asLog import eu.darken.capod.common.debug.logging.log import eu.darken.capod.common.debug.logging.logTag import eu.darken.capod.main.core.GeneralSettings +import eu.darken.capod.monitor.core.RPAChecker import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.apple.misc.UnknownAppleDevice import eu.darken.capod.pods.core.apple.protocol.ContinuityProtocol -import eu.darken.capod.pods.core.apple.protocol.MessageDecrypter +import eu.darken.capod.pods.core.apple.protocol.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import javax.inject.Inject @@ -20,15 +22,16 @@ import javax.inject.Singleton class AppleFactory @Inject constructor( private val continuityProtocolDecoder: ContinuityProtocol.Decoder, private val proximityPairingDecoder: ProximityPairing.Decoder, + private val proximityMessageDecrypter: ProximityMessage.Decrypter, private val podFactories: @JvmSuppressWildcards Set>, private val unknownAppleFactory: UnknownAppleDevice.Factory, private val generalSettings: GeneralSettings, - private val messageDecrypter: MessageDecrypter, + private val rpaChecker: RPAChecker, ) { private val lock = Mutex() - private fun getMessage(scanResult: BleScanResult): ProximityPairing.Message? { + private fun getMessage(scanResult: BleScanResult): ProximityMessage? { val messages = try { continuityProtocolDecoder.decode(scanResult) } catch (e: Exception) { @@ -54,20 +57,40 @@ class AppleFactory @Inject constructor( } suspend fun create(scanResult: BleScanResult): PodDevice? = lock.withLock { - val pm = getMessage(scanResult) ?: return@withLock null + val proximityMessage = getMessage(scanResult) ?: return@withLock null - val factory = podFactories.firstOrNull { it.isResponsible(pm) } + val factory = podFactories.firstOrNull { it.isResponsible(proximityMessage) } - val decryptedData = generalSettings.mainDeviceEncryptionKey.value - ?.let { messageDecrypter.decrypt(pm, it) } + val payload = getPayload(scanResult, proximityMessage) return@withLock (factory ?: unknownAppleFactory).create( scanResult = scanResult, - message = pm, - decrypted = decryptedData, + payload = payload, ) } + private fun getPayload( + scanResult: BleScanResult, + proximityMessage: ProximityMessage + ) = ProximityPayload( + public = ProximityPayload.Public( + proximityMessage.data.take(16).toUByteArray() + ), + private = run { + val irkKey = generalSettings.mainDeviceIdentityKey.value + if (irkKey == null) return@run null + val encKey = generalSettings.mainDeviceEncryptionKey.value + if (encKey == null) return@run null + + if (!rpaChecker.verify(scanResult.address, irkKey)) return@run null + + val encrypted = proximityMessage.data.takeLast(16).toUByteArray().toByteArray() + proximityMessageDecrypter.decrypt(encrypted, encKey)?.let { + ProximityPayload.Private(data = it) + } + }, + ) + companion object { private val TAG = logTag("Pod", "Apple", "Factory") } diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/ApplePods.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/ApplePods.kt index 84d2185e..c04fbaa3 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/ApplePods.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/ApplePods.kt @@ -1,51 +1,39 @@ package eu.darken.capod.pods.core.apple import eu.darken.capod.common.lowerNibble -import eu.darken.capod.common.toHex import eu.darken.capod.common.upperNibble import eu.darken.capod.pods.core.PodDevice -import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload interface ApplePods : PodDevice { - val proximityMessage: ProximityPairing.Message - - val decryptedPayload: UByteArray? - get() = null + val payload: ProximityPayload // We start counting at the airpods prefix byte - val rawPrefix: UByte - get() = proximityMessage.data[0] + val pubPrefix: UByte + get() = payload.public.data[0] - val rawDeviceModel: UShort - get() = (((proximityMessage.data[1].toInt() and 255) shl 8) or (proximityMessage.data[2].toInt() and 255)).toUShort() + val pubDeviceModel: UShort + get() = (((payload.public.data[1].toInt() and 255) shl 8) or (payload.public.data[2].toInt() and 255)).toUShort() - val rawStatus: UByte - get() = proximityMessage.data[3] + val pubStatus: UByte + get() = payload.public.data[3] - val rawStatusHex: String - get() = rawStatus.toHex() + val pubPodsBattery: UByte + get() = payload.public.data[4] - val rawPodsBattery: UByte - get() = proximityMessage.data[4] + val pubFlags: UShort + get() = payload.public.data[5].upperNibble - val rawPodsBatteryHex: String - get() = rawPodsBattery.toHex() + val pubCaseBattery: UShort + get() = payload.public.data[5].lowerNibble - val rawFlags: UShort - get() = proximityMessage.data[5].upperNibble - - val rawCaseBattery: UShort - get() = proximityMessage.data[5].lowerNibble - - val rawCaseLidState: UByte - get() = proximityMessage.data[6] - - val rawDeviceColor: UByte - get() = proximityMessage.data[7] - - val rawSuffix: UByte - get() = proximityMessage.data[8] + val pubCaseLidState: UByte + get() = payload.public.data[6] + val pubDeviceColor: UByte + get() = payload.public.data[7] + val pubSuffix: UByte + get() = payload.public.data[8] } \ No newline at end of file diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/ApplePodsFactory.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/ApplePodsFactory.kt index dde34f60..fddeac25 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/ApplePodsFactory.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/ApplePodsFactory.kt @@ -1,5 +1,6 @@ package eu.darken.capod.pods.core.apple +import android.R.id.message import eu.darken.capod.common.bluetooth.BleScanResult import eu.darken.capod.common.collections.median import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE @@ -9,30 +10,27 @@ import eu.darken.capod.common.lowerNibble import eu.darken.capod.common.upperNibble import eu.darken.capod.pods.core.HasCase import eu.darken.capod.pods.core.PodDevice -import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.protocol.ProximityMessage +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Duration import java.time.Instant import kotlin.math.max abstract class ApplePodsFactory(private val tag: String) { - data class Markings( - val vendor: UByte, - val length: UByte, + data class Identifier( val device: UShort, val podBatteryData: Set, val caseBatteryData: UShort, val deviceColor: UByte, ) - private fun ProximityPairing.Message.getApplePodsMarkings(): Markings = Markings( - vendor = ProximityPairing.CONTINUITY_PROTOCOL_MESSAGE_TYPE_PROXIMITY_PAIRING, - length = ProximityPairing.PAIRING_MESSAGE_LENGTH.toUByte(), - device = (((data[1].toInt() and 255) shl 8) or (data[2].toInt() and 255)).toUShort(), + private fun ProximityPayload.getFuzzyIdentifier(): Identifier = Identifier( + device = (((public.data[1].toInt() and 255) shl 8) or (public.data[2].toInt() and 255)).toUShort(), // Make comparison order independent - podBatteryData = setOf(data[4].upperNibble, data[4].lowerNibble), - caseBatteryData = data[5].lowerNibble, - deviceColor = data[7] + podBatteryData = setOf(public.data[4].upperNibble, public.data[4].lowerNibble), + caseBatteryData = public.data[5].lowerNibble, + deviceColor = public.data[7] ) data class KnownDevice( @@ -42,8 +40,8 @@ abstract class ApplePodsFactory(private val tag: String) { val history: List, val lastCaseBattery: Float?, ) { - val lastMessage: ProximityPairing.Message - get() = history.last().proximityMessage + val lastPayload: ProximityPayload + get() = history.last().payload val lastAddress: String get() = history.last().address @@ -127,7 +125,7 @@ abstract class ApplePodsFactory(private val tag: String) { internal open fun searchHistory(current: PodType): KnownDevice? { val scanResult = current.scanResult - val message = current.proximityMessage + val payload = current.payload knownDevices.values.toList().forEach { knownDevice -> if (knownDevice.isOlderThan(Duration.ofSeconds(30))) { @@ -148,9 +146,9 @@ abstract class ApplePodsFactory(private val tag: String) { ?.also { log(tag, VERBOSE) { "searchHistory1: Recovered previous ID via address: $it" } } if (recognizedDevice == null) { - val currentMarkers = message.getApplePodsMarkings() + val currentMarkers = payload.getFuzzyIdentifier() recognizedDevice = knownDevices.values - .firstOrNull { it.lastMessage.getApplePodsMarkings() == currentMarkers } + .firstOrNull { it.lastPayload.getFuzzyIdentifier() == currentMarkers } ?.also { log(tag) { "searchHistory1: Close match based on similarity: $currentMarkers" } } } @@ -173,6 +171,7 @@ abstract class ApplePodsFactory(private val tag: String) { lastCaseBattery = history.determineLatestCaseBattery() ?: existing.lastCaseBattery ) } + else -> { log(tag) { "searchHistory1: Creating new history for $device" } val history = listOf(device) @@ -192,16 +191,15 @@ abstract class ApplePodsFactory(private val tag: String) { val dirty: UByte, ) - fun ProximityPairing.Message.getModelInfo(): ModelInfo = ModelInfo( + fun ProximityMessage.getModelInfo(): ModelInfo = ModelInfo( full = (((data[1].toInt() and 255) shl 8) or (data[2].toInt() and 255)).toUShort(), dirty = data[1] ) - abstract fun isResponsible(message: ProximityPairing.Message): Boolean + abstract fun isResponsible(message: ProximityMessage): Boolean abstract fun create( scanResult: BleScanResult, - message: ProximityPairing.Message, - decrypted: UByteArray?, + payload: ProximityPayload, ): ApplePods } \ No newline at end of file diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/DualApplePods.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/DualApplePods.kt index c27b38da..0eb9bc90 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/DualApplePods.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/DualApplePods.kt @@ -15,7 +15,7 @@ interface DualApplePods : ApplePods, HasChargeDetectionDual, DualPodDevice, HasE HasDualMicrophone, HasAppleColor { val primaryPod: Pod - get() = when (rawStatus.isBitSet(5)) { + get() = when (pubStatus.isBitSet(5)) { true -> Pod.LEFT false -> Pod.RIGHT } @@ -25,17 +25,17 @@ interface DualApplePods : ApplePods, HasChargeDetectionDual, DualPodDevice, HasE * If the right pod is the primary, the values are flipped. */ val areValuesFlipped: Boolean - get() = !rawStatus.isBitSet(5) + get() = !pubStatus.isBitSet(5) override val batteryLeftPodPercent: Float? get() { - decryptedPayload?.get(if (areValuesFlipped) 2 else 1)?.let { raw -> + payload.private?.data?.get(if (areValuesFlipped) 2 else 1)?.let { raw -> val level = (raw and 0x7Fu).toInt() / 100f if (level <= 1.0f) return level } val value = when (areValuesFlipped) { - true -> rawPodsBattery.upperNibble.toInt() - false -> rawPodsBattery.lowerNibble.toInt() + true -> pubPodsBattery.upperNibble.toInt() + false -> pubPodsBattery.lowerNibble.toInt() } return when (value) { 15 -> null @@ -50,13 +50,13 @@ interface DualApplePods : ApplePods, HasChargeDetectionDual, DualPodDevice, HasE override val batteryRightPodPercent: Float? get() { - decryptedPayload?.get(if (areValuesFlipped) 1 else 2)?.let { raw -> + payload.private?.data?.get(if (areValuesFlipped) 1 else 2)?.let { raw -> val level = (raw and 0x7Fu).toInt() / 100f if (level <= 1.0f) return level } val value = when (areValuesFlipped) { - true -> rawPodsBattery.lowerNibble.toInt() - false -> rawPodsBattery.upperNibble.toInt() + true -> pubPodsBattery.lowerNibble.toInt() + false -> pubPodsBattery.upperNibble.toInt() } return when (value) { 15 -> null @@ -70,24 +70,24 @@ interface DualApplePods : ApplePods, HasChargeDetectionDual, DualPodDevice, HasE } val isThisPodInThecase: Boolean - get() = rawStatus.isBitSet(6) + get() = pubStatus.isBitSet(6) val isOnePodInCase: Boolean - get() = rawStatus.isBitSet(4) + get() = pubStatus.isBitSet(4) val areBothPodsInCase: Boolean - get() = rawStatus.isBitSet(2) + get() = pubStatus.isBitSet(2) override val isLeftPodInEar: Boolean get() = when (areValuesFlipped xor isThisPodInThecase) { - true -> rawStatus.isBitSet(3) - false -> rawStatus.isBitSet(1) + true -> pubStatus.isBitSet(3) + false -> pubStatus.isBitSet(1) } override val isRightPodInEar: Boolean get() = when (areValuesFlipped xor isThisPodInThecase) { - true -> rawStatus.isBitSet(1) - false -> rawStatus.isBitSet(3) + true -> pubStatus.isBitSet(1) + false -> pubStatus.isBitSet(3) } /** @@ -95,46 +95,46 @@ interface DualApplePods : ApplePods, HasChargeDetectionDual, DualPodDevice, HasE * For the pod that is in the case, this is flipped again though. */ override val isLeftPodMicrophone: Boolean - get() = rawStatus.isBitSet(5) xor isThisPodInThecase + get() = pubStatus.isBitSet(5) xor isThisPodInThecase /** * The data flip bit is UNset if the right pod is primary. * For the pod that is in the case, this is flipped again though. */ override val isRightPodMicrophone: Boolean - get() = !rawStatus.isBitSet(5) xor isThisPodInThecase + get() = !pubStatus.isBitSet(5) xor isThisPodInThecase override val isLeftPodCharging: Boolean get() { - decryptedPayload?.get(if (areValuesFlipped) 2 else 1)?.let { raw -> + payload.private?.data?.get(if (areValuesFlipped) 2 else 1)?.let { raw -> val isCharging = (raw and 0x80u).toInt() != 0 return isCharging } return when (areValuesFlipped) { - false -> rawFlags.isBitSet(0) - true -> rawFlags.isBitSet(1) + false -> pubFlags.isBitSet(0) + true -> pubFlags.isBitSet(1) } } override val isRightPodCharging: Boolean get() { - decryptedPayload?.get(if (areValuesFlipped) 1 else 2)?.let { raw -> + payload.private?.data?.get(if (areValuesFlipped) 1 else 2)?.let { raw -> val isCharging = (raw and 0x80u).toInt() != 0 return isCharging } return when (areValuesFlipped) { - false -> rawFlags.isBitSet(1) - true -> rawFlags.isBitSet(0) + false -> pubFlags.isBitSet(1) + true -> pubFlags.isBitSet(0) } } override val batteryCasePercent: Float? get() { - decryptedPayload?.get(3)?.let { raw -> + payload.private?.data?.get(3)?.let { raw -> val level = (raw and 0x7Fu).toInt() / 100f if (level <= 1.0f) return level } - return when (val value = rawCaseBattery.toInt()) { + return when (val value = pubCaseBattery.toInt()) { 15 -> null else -> if (value > 10) { log { "Case: Above 100% battery: $value" } @@ -147,18 +147,18 @@ interface DualApplePods : ApplePods, HasChargeDetectionDual, DualPodDevice, HasE override val isCaseCharging: Boolean get() { - decryptedPayload?.get(3)?.let { raw -> + payload.private?.data?.get(3)?.let { raw -> val isCharging = (raw and 0x80u).toInt() != 0 if (batteryCasePercent != null) return isCharging } - return rawFlags.isBitSet(2) + return pubFlags.isBitSet(2) } val caseLidState: LidState get() { - val rawstate = rawCaseLidState - return LidState.values().firstOrNull { it.rawRange.contains(rawstate.toInt()) } ?: LidState.UNKNOWN + val rawstate = pubCaseLidState + return LidState.entries.firstOrNull { it.rawRange.contains(rawstate.toInt()) } ?: LidState.UNKNOWN } /** diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/DualApplePodsFactory.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/DualApplePodsFactory.kt index c67d50f0..9bf266e0 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/DualApplePodsFactory.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/DualApplePodsFactory.kt @@ -13,7 +13,7 @@ abstract class DualApplePodsFactory(private val tag: String) : ApplePodsFactory< microPhoneRight = isRightPodMicrophone, chargingLeft = isLeftPodCharging, chargingRight = isRightPodCharging, - color = rawDeviceColor, + color = pubDeviceColor, model = model ) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/HasAppleColor.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/HasAppleColor.kt index 36329979..ac174839 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/HasAppleColor.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/HasAppleColor.kt @@ -6,8 +6,8 @@ import eu.darken.capod.pods.core.HasPodStyle interface HasAppleColor : ApplePods, HasPodStyle { override val podStyle: HasPodStyle.PodStyle - get() = DeviceColor.values() - .firstOrNull { it.raw == rawDeviceColor } + get() = DeviceColor.entries + .firstOrNull { it.raw == pubDeviceColor } ?: DeviceColor.UNKNOWN enum class DeviceColor(val raw: UByte?) : HasPodStyle.PodStyle { diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/SingleApplePods.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/SingleApplePods.kt index eea441b0..6d01d43f 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/SingleApplePods.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/SingleApplePods.kt @@ -10,7 +10,7 @@ import eu.darken.capod.pods.core.SinglePodDevice interface SingleApplePods : ApplePods, SinglePodDevice, HasAppleColor { override val batteryHeadsetPercent: Float? - get() = when (val value = rawPodsBattery.lowerNibble.toInt()) { + get() = when (val value = pubPodsBattery.lowerNibble.toInt()) { 15 -> null else -> if (value > 10) { log { "Headset above 100% battery: $value" } diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen1.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen1.kt index 4502acb9..93bbf023 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen1.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen1.kt @@ -6,17 +6,19 @@ import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.DualApplePods import eu.darken.capod.pods.core.apple.DualApplePodsFactory +import eu.darken.capod.pods.core.apple.protocol.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant import javax.inject.Inject -data class AirPodsGen1 constructor( +data class AirPodsGen1( 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 payload: ProximityPayload, override val reliability: Float = PodDevice.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, @@ -36,17 +38,16 @@ data class AirPodsGen1 constructor( class Factory @Inject constructor() : DualApplePodsFactory(TAG) { - override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } override fun create( scanResult: BleScanResult, - message: ProximityPairing.Message, - decrypted: UByteArray? + payload: ProximityPayload ): ApplePods { - var basic = AirPodsGen1(scanResult = scanResult, proximityMessage = message) + var basic = AirPodsGen1(scanResult = scanResult, payload = payload) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen2.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen2.kt index 1c1f012a..2a411297 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen2.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen2.kt @@ -6,17 +6,19 @@ import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.DualApplePods import eu.darken.capod.pods.core.apple.DualApplePodsFactory +import eu.darken.capod.pods.core.apple.protocol.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant import javax.inject.Inject -data class AirPodsGen2 constructor( +data class AirPodsGen2( 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 payload: ProximityPayload, override val reliability: Float = PodDevice.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, @@ -36,16 +38,15 @@ data class AirPodsGen2 constructor( class Factory @Inject constructor() : DualApplePodsFactory(TAG) { - override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } override fun create( scanResult: BleScanResult, - message: ProximityPairing.Message, - decrypted: UByteArray? + payload: ProximityPayload ): ApplePods { - var basic = AirPodsGen2(scanResult = scanResult, proximityMessage = message) + var basic = AirPodsGen2(scanResult = scanResult, payload = payload) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen3.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen3.kt index eb340d07..50180d77 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen3.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen3.kt @@ -6,17 +6,19 @@ import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.DualApplePods import eu.darken.capod.pods.core.apple.DualApplePodsFactory +import eu.darken.capod.pods.core.apple.protocol.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant import javax.inject.Inject -data class AirPodsGen3 constructor( +data class AirPodsGen3( 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 payload: ProximityPayload, override val reliability: Float = PodDevice.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, @@ -36,16 +38,15 @@ data class AirPodsGen3 constructor( class Factory @Inject constructor() : DualApplePodsFactory(TAG) { - override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } override fun create( scanResult: BleScanResult, - message: ProximityPairing.Message, - decrypted: UByteArray? + payload: ProximityPayload ): ApplePods { - var basic = AirPodsGen3(scanResult = scanResult, proximityMessage = message) + var basic = AirPodsGen3(scanResult = scanResult, payload = payload) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4.kt index 569653a8..2be39b84 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4.kt @@ -6,7 +6,9 @@ import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.DualApplePods import eu.darken.capod.pods.core.apple.DualApplePodsFactory +import eu.darken.capod.pods.core.apple.protocol.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant import javax.inject.Inject @@ -16,7 +18,7 @@ data class AirPodsGen4( override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, - override val proximityMessage: ProximityPairing.Message, + override val payload: ProximityPayload, override val reliability: Float = PodDevice.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, @@ -36,16 +38,15 @@ data class AirPodsGen4( class Factory @Inject constructor() : DualApplePodsFactory(TAG) { - override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } override fun create( scanResult: BleScanResult, - message: ProximityPairing.Message, - decrypted: UByteArray? + payload: ProximityPayload ): ApplePods { - var basic = AirPodsGen4(scanResult = scanResult, proximityMessage = message) + var basic = AirPodsGen4(scanResult = scanResult, payload = payload) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4Anc.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4Anc.kt index aacc1e54..e7c94095 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4Anc.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4Anc.kt @@ -6,7 +6,9 @@ import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.DualApplePods import eu.darken.capod.pods.core.apple.DualApplePodsFactory +import eu.darken.capod.pods.core.apple.protocol.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant import javax.inject.Inject @@ -16,7 +18,7 @@ data class AirPodsGen4Anc( override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, - override val proximityMessage: ProximityPairing.Message, + override val payload: ProximityPayload, override val reliability: Float = PodDevice.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, @@ -36,16 +38,15 @@ data class AirPodsGen4Anc( class Factory @Inject constructor() : DualApplePodsFactory(TAG) { - override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } override fun create( scanResult: BleScanResult, - message: ProximityPairing.Message, - decrypted: UByteArray? + payload: ProximityPayload ): ApplePods { - var basic = AirPodsGen4Anc(scanResult = scanResult, proximityMessage = message) + var basic = AirPodsGen4Anc(scanResult = scanResult, payload = payload) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMax.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMax.kt index 051957fe..a7a1c651 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMax.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMax.kt @@ -10,7 +10,9 @@ 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.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant import javax.inject.Inject @@ -20,7 +22,7 @@ data class AirPodsMax( override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, - override val proximityMessage: ProximityPairing.Message, + override val payload: ProximityPayload, override val reliability: Float = PodDevice.BASE_CONFIDENCE, private val rssiAverage: Int? = null, ) : SingleApplePods, HasEarDetection, HasChargeDetection, HasAppleColor { @@ -31,23 +33,22 @@ data class AirPodsMax( get() = rssiAverage ?: super.rssi override val isHeadsetBeingCharged: Boolean - get() = rawFlags.isBitSet(0) + get() = pubFlags.isBitSet(0) override val isBeingWorn: Boolean - get() = rawStatus.isBitSet(5) + get() = pubStatus.isBitSet(5) class Factory @Inject constructor() : SingleApplePodsFactory(TAG) { - override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } override fun create( scanResult: BleScanResult, - message: ProximityPairing.Message, - decrypted: UByteArray? + payload: ProximityPayload ): ApplePods { - var basic = AirPodsMax(scanResult = scanResult, proximityMessage = message) + var basic = AirPodsMax(scanResult = scanResult, payload = payload) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMaxUsbc.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMaxUsbc.kt index 77348bc4..fab35f8b 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMaxUsbc.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMaxUsbc.kt @@ -10,7 +10,9 @@ 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.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant import javax.inject.Inject @@ -20,7 +22,7 @@ data class AirPodsMaxUsbc( override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, - override val proximityMessage: ProximityPairing.Message, + override val payload: ProximityPayload, override val reliability: Float = PodDevice.BASE_CONFIDENCE, private val rssiAverage: Int? = null, ) : SingleApplePods, HasEarDetection, HasChargeDetection, HasAppleColor { @@ -31,23 +33,22 @@ data class AirPodsMaxUsbc( get() = rssiAverage ?: super.rssi override val isHeadsetBeingCharged: Boolean - get() = rawFlags.isBitSet(0) + get() = pubFlags.isBitSet(0) override val isBeingWorn: Boolean - get() = rawStatus.isBitSet(5) + get() = pubStatus.isBitSet(5) class Factory @Inject constructor() : SingleApplePodsFactory(TAG) { - override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } override fun create( scanResult: BleScanResult, - message: ProximityPairing.Message, - decrypted: UByteArray? + payload: ProximityPayload ): ApplePods { - var basic = AirPodsMaxUsbc(scanResult = scanResult, proximityMessage = message) + var basic = AirPodsMaxUsbc(scanResult = scanResult, payload = payload) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro.kt index 648c2e8f..dfd2b19a 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro.kt @@ -9,7 +9,9 @@ import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.DualApplePods import eu.darken.capod.pods.core.apple.DualApplePods.LidState import eu.darken.capod.pods.core.apple.DualApplePodsFactory +import eu.darken.capod.pods.core.apple.protocol.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant import javax.inject.Inject @@ -19,8 +21,7 @@ data class AirPodsPro( override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, - override val proximityMessage: ProximityPairing.Message, - override val decryptedPayload: UByteArray?, + override val payload: ProximityPayload, override val reliability: Float = PodDevice.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, @@ -56,19 +57,17 @@ data class AirPodsPro( class Factory @Inject constructor() : DualApplePodsFactory(TAG) { - override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } override fun create( scanResult: BleScanResult, - message: ProximityPairing.Message, - decrypted: UByteArray? + payload: ProximityPayload ): ApplePods { var basic = AirPodsPro( scanResult = scanResult, - proximityMessage = message, - decryptedPayload = decrypted + payload = payload, ) val result = searchHistory(basic) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2.kt index de40e69c..509fda2d 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2.kt @@ -9,7 +9,9 @@ import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.DualApplePods import eu.darken.capod.pods.core.apple.DualApplePods.LidState import eu.darken.capod.pods.core.apple.DualApplePodsFactory +import eu.darken.capod.pods.core.apple.protocol.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant import javax.inject.Inject @@ -19,7 +21,7 @@ data class AirPodsPro2( override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, - override val proximityMessage: ProximityPairing.Message, + override val payload: ProximityPayload, override val reliability: Float = PodDevice.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, @@ -55,16 +57,15 @@ data class AirPodsPro2( class Factory @Inject constructor() : DualApplePodsFactory(TAG) { - override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } override fun create( scanResult: BleScanResult, - message: ProximityPairing.Message, - decrypted: UByteArray? + payload: ProximityPayload ): ApplePods { - var basic = AirPodsPro2(scanResult = scanResult, proximityMessage = message) + var basic = AirPodsPro2(scanResult = scanResult, payload = payload) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2Usbc.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2Usbc.kt index 386c8826..50cf07d1 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2Usbc.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2Usbc.kt @@ -9,7 +9,9 @@ import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.DualApplePods import eu.darken.capod.pods.core.apple.DualApplePods.LidState import eu.darken.capod.pods.core.apple.DualApplePodsFactory +import eu.darken.capod.pods.core.apple.protocol.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant import javax.inject.Inject @@ -19,7 +21,7 @@ data class AirPodsPro2Usbc( override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, - override val proximityMessage: ProximityPairing.Message, + override val payload: ProximityPayload, override val reliability: Float = PodDevice.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, @@ -55,16 +57,15 @@ data class AirPodsPro2Usbc( class Factory @Inject constructor() : DualApplePodsFactory(TAG) { - override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } override fun create( scanResult: BleScanResult, - message: ProximityPairing.Message, - decrypted: UByteArray? + payload: ProximityPayload ): ApplePods { - var basic = AirPodsPro2Usbc(scanResult = scanResult, proximityMessage = message) + var basic = AirPodsPro2Usbc(scanResult = scanResult, payload = payload) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/HasStateDetectionAirPods.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/HasStateDetectionAirPods.kt index 92781a74..9ee5246d 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/HasStateDetectionAirPods.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/HasStateDetectionAirPods.kt @@ -9,7 +9,7 @@ import eu.darken.capod.pods.core.apple.ApplePods interface HasStateDetectionAirPods : HasStateDetection, ApplePods { override val state: ConnectionState - get() = ConnectionState.values().firstOrNull { rawSuffix == it.raw } ?: ConnectionState.UNKNOWN + get() = ConnectionState.entries.firstOrNull { pubSuffix == it.raw } ?: ConnectionState.UNKNOWN enum class ConnectionState(val raw: UByte?, @StringRes val labelRes: Int) : HasStateDetection.State { DISCONNECTED(0x00, R.string.pods_connection_state_disconnected_label), diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFitPro.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFitPro.kt index ce5deb2f..aa2b5f59 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFitPro.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFitPro.kt @@ -6,7 +6,9 @@ import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.DualApplePods import eu.darken.capod.pods.core.apple.DualApplePodsFactory +import eu.darken.capod.pods.core.apple.protocol.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant import javax.inject.Inject @@ -16,7 +18,7 @@ data class BeatsFitPro( override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, - override val proximityMessage: ProximityPairing.Message, + override val payload: ProximityPayload, override val reliability: Float = PodDevice.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, @@ -36,16 +38,15 @@ data class BeatsFitPro( class Factory @Inject constructor() : DualApplePodsFactory(TAG) { - override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } override fun create( scanResult: BleScanResult, - message: ProximityPairing.Message, - decrypted: UByteArray? + payload: ProximityPayload ): ApplePods { - var basic = BeatsFitPro(scanResult = scanResult, proximityMessage = message) + var basic = BeatsFitPro(scanResult = scanResult, payload = payload) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFlex.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFlex.kt index 52a92239..fca06a6a 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFlex.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFlex.kt @@ -6,7 +6,9 @@ import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.SingleApplePods import eu.darken.capod.pods.core.apple.SingleApplePodsFactory +import eu.darken.capod.pods.core.apple.protocol.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant import javax.inject.Inject @@ -16,7 +18,7 @@ data class BeatsFlex( override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, - override val proximityMessage: ProximityPairing.Message, + override val payload: ProximityPayload, override val reliability: Float = PodDevice.BASE_CONFIDENCE, private val rssiAverage: Int? = null, ) : SingleApplePods { @@ -28,16 +30,15 @@ data class BeatsFlex( class Factory @Inject constructor() : SingleApplePodsFactory(TAG) { - override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } override fun create( scanResult: BleScanResult, - message: ProximityPairing.Message, - decrypted: UByteArray? + payload: ProximityPayload ): ApplePods { - var basic = BeatsFlex(scanResult = scanResult, proximityMessage = message) + var basic = BeatsFlex(scanResult = scanResult, payload = payload) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsSolo3.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsSolo3.kt index 2e026583..e38fcbbc 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsSolo3.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsSolo3.kt @@ -6,7 +6,9 @@ import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.SingleApplePods import eu.darken.capod.pods.core.apple.SingleApplePodsFactory +import eu.darken.capod.pods.core.apple.protocol.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant import javax.inject.Inject @@ -16,7 +18,7 @@ data class BeatsSolo3( override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, - override val proximityMessage: ProximityPairing.Message, + override val payload: ProximityPayload, override val reliability: Float = PodDevice.BASE_CONFIDENCE, private val rssiAverage: Int? = null, ) : SingleApplePods { @@ -27,16 +29,15 @@ data class BeatsSolo3( class Factory @Inject constructor() : SingleApplePodsFactory(TAG) { - override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } override fun create( scanResult: BleScanResult, - message: ProximityPairing.Message, - decrypted: UByteArray? + payload: ProximityPayload ): ApplePods { - var basic = BeatsSolo3(scanResult = scanResult, proximityMessage = message) + var basic = BeatsSolo3(scanResult = scanResult, payload = payload) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsStudio3.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsStudio3.kt index b0c2f649..773f20b8 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsStudio3.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsStudio3.kt @@ -6,7 +6,9 @@ import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.SingleApplePods import eu.darken.capod.pods.core.apple.SingleApplePodsFactory +import eu.darken.capod.pods.core.apple.protocol.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant import javax.inject.Inject @@ -16,7 +18,7 @@ data class BeatsStudio3( override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, - override val proximityMessage: ProximityPairing.Message, + override val payload: ProximityPayload, override val reliability: Float = PodDevice.BASE_CONFIDENCE, private val rssiAverage: Int? = null, ) : SingleApplePods { @@ -25,16 +27,15 @@ data class BeatsStudio3( class Factory @Inject constructor() : SingleApplePodsFactory(TAG) { - override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().dirty == DEVICE_CODE_DIRTY && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } override fun create( scanResult: BleScanResult, - message: ProximityPairing.Message, - decrypted: UByteArray? + payload: ProximityPayload ): ApplePods { - var basic = BeatsStudio3(scanResult = scanResult, proximityMessage = message) + var basic = BeatsStudio3(scanResult = scanResult, payload = payload) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsX.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsX.kt index 74c63a00..12238c88 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsX.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsX.kt @@ -6,7 +6,9 @@ import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.SingleApplePods import eu.darken.capod.pods.core.apple.SingleApplePodsFactory +import eu.darken.capod.pods.core.apple.protocol.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant import javax.inject.Inject @@ -16,7 +18,7 @@ data class BeatsX( override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, - override val proximityMessage: ProximityPairing.Message, + override val payload: ProximityPayload, override val reliability: Float = PodDevice.BASE_CONFIDENCE, private val rssiAverage: Int? = null, ) : SingleApplePods { @@ -28,16 +30,15 @@ data class BeatsX( class Factory @Inject constructor() : SingleApplePodsFactory(TAG) { - override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } override fun create( scanResult: BleScanResult, - message: ProximityPairing.Message, - decrypted: UByteArray? + payload: ProximityPayload ): ApplePods { - var basic = BeatsX(scanResult = scanResult, proximityMessage = message) + var basic = BeatsX(scanResult = scanResult, payload = payload) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats3.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats3.kt index 6b4c0d4e..c1972522 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats3.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats3.kt @@ -6,7 +6,9 @@ import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.SingleApplePods import eu.darken.capod.pods.core.apple.SingleApplePodsFactory +import eu.darken.capod.pods.core.apple.protocol.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant import javax.inject.Inject @@ -16,7 +18,7 @@ data class PowerBeats3( override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, - override val proximityMessage: ProximityPairing.Message, + override val payload: ProximityPayload, override val reliability: Float = PodDevice.BASE_CONFIDENCE, private val rssiAverage: Int? = null, ) : SingleApplePods { @@ -28,16 +30,15 @@ data class PowerBeats3( class Factory @Inject constructor() : SingleApplePodsFactory(TAG) { - override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } override fun create( scanResult: BleScanResult, - message: ProximityPairing.Message, - decrypted: UByteArray? + payload: ProximityPayload ): ApplePods { - var basic = PowerBeats3(scanResult = scanResult, proximityMessage = message) + var basic = PowerBeats3(scanResult = scanResult, payload = payload) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats4.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats4.kt index 5f159fb6..460f4ac3 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats4.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats4.kt @@ -7,7 +7,9 @@ import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.SingleApplePods import eu.darken.capod.pods.core.apple.SingleApplePodsFactory import eu.darken.capod.pods.core.apple.airpods.HasStateDetectionAirPods +import eu.darken.capod.pods.core.apple.protocol.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant import javax.inject.Inject @@ -17,7 +19,7 @@ data class PowerBeats4( override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, - override val proximityMessage: ProximityPairing.Message, + override val payload: ProximityPayload, override val reliability: Float = PodDevice.BASE_CONFIDENCE, private val rssiAverage: Int? = null, ) : SingleApplePods, HasStateDetectionAirPods { @@ -29,16 +31,15 @@ data class PowerBeats4( class Factory @Inject constructor() : SingleApplePodsFactory(TAG) { - override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } override fun create( scanResult: BleScanResult, - message: ProximityPairing.Message, - decrypted: UByteArray? + payload: ProximityPayload ): ApplePods { - var basic = PowerBeats4(scanResult = scanResult, proximityMessage = message) + var basic = PowerBeats4(scanResult = scanResult, payload = payload) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro.kt index 5a8b94a3..ddf5d305 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro.kt @@ -6,7 +6,9 @@ import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.DualApplePods import eu.darken.capod.pods.core.apple.DualApplePodsFactory +import eu.darken.capod.pods.core.apple.protocol.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant import javax.inject.Inject @@ -16,7 +18,7 @@ data class PowerBeatsPro( override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, - override val proximityMessage: ProximityPairing.Message, + override val payload: ProximityPayload, override val reliability: Float = PodDevice.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, @@ -36,16 +38,15 @@ data class PowerBeatsPro( class Factory @Inject constructor() : DualApplePodsFactory(TAG) { - override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().dirty == DEVICE_CODE_DIRTY && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } override fun create( scanResult: BleScanResult, - message: ProximityPairing.Message, - decrypted: UByteArray? + payload: ProximityPayload ): ApplePods { - var basic = PowerBeatsPro(scanResult = scanResult, proximityMessage = message) + var basic = PowerBeatsPro(scanResult = scanResult, payload = payload) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen1.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen1.kt index c07e93e1..9c6eac8f 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen1.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen1.kt @@ -1,15 +1,16 @@ package eu.darken.capod.pods.core.apple.misc import eu.darken.capod.common.bluetooth.BleScanResult -import eu.darken.capod.common.debug.logging.log import eu.darken.capod.common.debug.logging.logTag -import eu.darken.capod.common.isBitSet -import eu.darken.capod.common.lowerNibble -import eu.darken.capod.common.upperNibble -import eu.darken.capod.pods.core.* +import eu.darken.capod.pods.core.HasCase +import eu.darken.capod.pods.core.HasChargeDetectionDual +import eu.darken.capod.pods.core.HasEarDetectionDual +import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory -import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.DualApplePods +import eu.darken.capod.pods.core.apple.protocol.ProximityMessage +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant import javax.inject.Inject @@ -18,118 +19,38 @@ import javax.inject.Inject * Similar data structure but a lot of placeholder values or hardcoded values * Marketed as TWS i99999 */ -data class FakeAirPodsGen1 constructor( +data class FakeAirPodsGen1( 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 payload: ProximityPayload, override val reliability: Float = PodDevice.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, -) : ApplePods, DualPodDevice, HasEarDetectionDual, HasChargeDetectionDual, HasCase { +) : DualApplePods, HasEarDetectionDual, HasChargeDetectionDual, HasCase { override val model: PodDevice.Model = PodDevice.Model.FAKE_AIRPODS_GEN1 - override val rssi: Int - get() = rssiAverage ?: super.rssi - - /** - * Normally values for the left pod are in the lower nibbles, if the left pod is primary (microphone) - * If the right pod is the primary, the values are flipped. - */ - val areValuesFlipped: Boolean - get() = !rawStatus.isBitSet(5) - - override val batteryLeftPodPercent: Float? - get() { - val value = when (areValuesFlipped) { - true -> rawPodsBattery.upperNibble.toInt() - false -> rawPodsBattery.lowerNibble.toInt() - } - return when (value) { - 15 -> null - else -> if (value > 10) { - log { "Left pod: Above 100% battery: $value" } - 1.0f - } else { - (value / 10f) - } - } - } - - override val batteryRightPodPercent: Float? - get() { - val value = when (areValuesFlipped) { - true -> rawPodsBattery.lowerNibble.toInt() - false -> rawPodsBattery.upperNibble.toInt() - } - return when (value) { - 15 -> null - else -> if (value > 10) { - log { "Right pod: Above 100% battery: $value" } - 1.0f - } else { - value / 10f - } - } - } - - private val isThisPodInThecase: Boolean - get() = rawStatus.isBitSet(6) - - override val isLeftPodInEar: Boolean - get() = when (areValuesFlipped xor isThisPodInThecase) { - true -> rawStatus.isBitSet(3) - false -> rawStatus.isBitSet(1) - } - - override val isRightPodInEar: Boolean - get() = when (areValuesFlipped xor isThisPodInThecase) { - true -> rawStatus.isBitSet(1) - false -> rawStatus.isBitSet(3) - } - - override val isLeftPodCharging: Boolean - get() = when (areValuesFlipped) { - false -> rawFlags.isBitSet(0) - true -> rawFlags.isBitSet(1) - } - - override val isRightPodCharging: Boolean - get() = when (areValuesFlipped) { - false -> rawFlags.isBitSet(1) - true -> rawFlags.isBitSet(0) - } - override val batteryCasePercent: Float? - get() = when (val value = rawCaseBattery.toInt()) { - 15 -> cachedBatteryPercentage - else -> if (value > 10) { - log { "Case: Above 100% battery: $value" } - 1.0f - } else { - value / 10f - } - } + get() = super.batteryCasePercent ?: cachedBatteryPercentage - override val isCaseCharging: Boolean - get() = rawFlags.isBitSet(2) + override val rssi: Int + get() = rssiAverage ?: super.rssi class Factory @Inject constructor() : ApplePodsFactory(TAG) { - override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + override fun isResponsible(message: ProximityMessage): Boolean = message.run { // Official message length is 19HEX, i.e. binary 25, did they copy this wrong? getModelInfo().full == DEVICE_CODE && length == 19 } override fun create( scanResult: BleScanResult, - message: ProximityPairing.Message, - decrypted: UByteArray? + payload: ProximityPayload ): ApplePods { - var basic = FakeAirPodsGen1(scanResult = scanResult, proximityMessage = message) + var basic = FakeAirPodsGen1(scanResult = scanResult, payload = payload) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen2.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen2.kt index 95a396cd..a6095ba7 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen2.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen2.kt @@ -1,133 +1,54 @@ package eu.darken.capod.pods.core.apple.misc import eu.darken.capod.common.bluetooth.BleScanResult -import eu.darken.capod.common.debug.logging.log import eu.darken.capod.common.debug.logging.logTag -import eu.darken.capod.common.isBitSet -import eu.darken.capod.common.lowerNibble -import eu.darken.capod.common.upperNibble -import eu.darken.capod.pods.core.* +import eu.darken.capod.pods.core.HasCase +import eu.darken.capod.pods.core.HasChargeDetectionDual +import eu.darken.capod.pods.core.HasEarDetectionDual +import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory -import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.DualApplePods +import eu.darken.capod.pods.core.apple.protocol.ProximityMessage +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant import javax.inject.Inject /** * Similar data structure but a lot of placeholder values or hardcoded values */ -data class FakeAirPodsGen2 constructor( +data class FakeAirPodsGen2( 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 payload: ProximityPayload, override val reliability: Float = PodDevice.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, -) : ApplePods, DualPodDevice, HasEarDetectionDual, HasChargeDetectionDual, HasCase { +) : DualApplePods, HasEarDetectionDual, HasChargeDetectionDual, HasCase { override val model: PodDevice.Model = PodDevice.Model.FAKE_AIRPODS_GEN2 override val rssi: Int - get() = rssiAverage ?: super.rssi - - /** - * Normally values for the left pod are in the lower nibbles, if the left pod is primary (microphone) - * If the right pod is the primary, the values are flipped. - */ - val areValuesFlipped: Boolean - get() = !rawStatus.isBitSet(5) - - override val batteryLeftPodPercent: Float? - get() { - val value = when (areValuesFlipped) { - true -> rawPodsBattery.upperNibble.toInt() - false -> rawPodsBattery.lowerNibble.toInt() - } - return when (value) { - 15 -> null - else -> if (value > 10) { - log { "Left pod: Above 100% battery: $value" } - 1.0f - } else { - (value / 10f) - } - } - } - - override val batteryRightPodPercent: Float? - get() { - val value = when (areValuesFlipped) { - true -> rawPodsBattery.lowerNibble.toInt() - false -> rawPodsBattery.upperNibble.toInt() - } - return when (value) { - 15 -> null - else -> if (value > 10) { - log { "Right pod: Above 100% battery: $value" } - 1.0f - } else { - value / 10f - } - } - } - - private val isThisPodInThecase: Boolean - get() = rawStatus.isBitSet(6) - - override val isLeftPodInEar: Boolean - get() = when (areValuesFlipped xor isThisPodInThecase) { - true -> rawStatus.isBitSet(3) - false -> rawStatus.isBitSet(1) - } - - override val isRightPodInEar: Boolean - get() = when (areValuesFlipped xor isThisPodInThecase) { - true -> rawStatus.isBitSet(1) - false -> rawStatus.isBitSet(3) - } - - override val isLeftPodCharging: Boolean - get() = when (areValuesFlipped) { - false -> rawFlags.isBitSet(0) - true -> rawFlags.isBitSet(1) - } - - override val isRightPodCharging: Boolean - get() = when (areValuesFlipped) { - false -> rawFlags.isBitSet(1) - true -> rawFlags.isBitSet(0) - } + get() = rssiAverage ?: super.rssi override val batteryCasePercent: Float? - get() = when (val value = rawCaseBattery.toInt()) { - 15 -> cachedBatteryPercentage - else -> if (value > 10) { - log { "Case: Above 100% battery: $value" } - 1.0f - } else { - value / 10f - } - } - - override val isCaseCharging: Boolean - get() = rawFlags.isBitSet(2) + get() = super.batteryCasePercent ?: cachedBatteryPercentage class Factory @Inject constructor() : ApplePodsFactory(TAG) { - override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + override fun isResponsible(message: ProximityMessage): Boolean = message.run { // Official message length is 19HEX, i.e. binary 25, did they copy this wrong? getModelInfo().full == DEVICE_CODE && length == 19 } override fun create( scanResult: BleScanResult, - message: ProximityPairing.Message, - decrypted: UByteArray? + payload: ProximityPayload ): ApplePods { - var basic = FakeAirPodsGen2(scanResult = scanResult, proximityMessage = message) + var basic = FakeAirPodsGen2(scanResult = scanResult, payload = payload) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen3.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen3.kt index 2db60339..c27a6334 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen3.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen3.kt @@ -1,15 +1,16 @@ package eu.darken.capod.pods.core.apple.misc import eu.darken.capod.common.bluetooth.BleScanResult -import eu.darken.capod.common.debug.logging.log import eu.darken.capod.common.debug.logging.logTag -import eu.darken.capod.common.isBitSet -import eu.darken.capod.common.lowerNibble -import eu.darken.capod.common.upperNibble -import eu.darken.capod.pods.core.* +import eu.darken.capod.pods.core.HasCase +import eu.darken.capod.pods.core.HasChargeDetectionDual +import eu.darken.capod.pods.core.HasEarDetectionDual +import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory -import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.DualApplePods +import eu.darken.capod.pods.core.apple.protocol.ProximityMessage +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant import javax.inject.Inject @@ -18,118 +19,38 @@ import javax.inject.Inject * Shorter data structure. * https://discord.com/channels/548521543039189022/927235844127993866/1054404630118924308 */ -data class FakeAirPodsGen3 constructor( +data class FakeAirPodsGen3( 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 payload: ProximityPayload, override val reliability: Float = PodDevice.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, -) : ApplePods, DualPodDevice, HasEarDetectionDual, HasChargeDetectionDual, HasCase { +) : DualApplePods, HasEarDetectionDual, HasChargeDetectionDual, HasCase { override val model: PodDevice.Model = PodDevice.Model.FAKE_AIRPODS_GEN3 override val rssi: Int - get() = rssiAverage ?: super.rssi - - /** - * Normally values for the left pod are in the lower nibbles, if the left pod is primary (microphone) - * If the right pod is the primary, the values are flipped. - */ - val areValuesFlipped: Boolean - get() = !rawStatus.isBitSet(5) - - override val batteryLeftPodPercent: Float? - get() { - val value = when (areValuesFlipped) { - true -> rawPodsBattery.upperNibble.toInt() - false -> rawPodsBattery.lowerNibble.toInt() - } - return when (value) { - 15 -> null - else -> if (value > 10) { - log { "Left pod: Above 100% battery: $value" } - 1.0f - } else { - (value / 10f) - } - } - } - - override val batteryRightPodPercent: Float? - get() { - val value = when (areValuesFlipped) { - true -> rawPodsBattery.lowerNibble.toInt() - false -> rawPodsBattery.upperNibble.toInt() - } - return when (value) { - 15 -> null - else -> if (value > 10) { - log { "Right pod: Above 100% battery: $value" } - 1.0f - } else { - value / 10f - } - } - } - - override val isLeftPodCharging: Boolean - get() = when (areValuesFlipped) { - false -> rawFlags.isBitSet(0) - true -> rawFlags.isBitSet(1) - } - - override val isRightPodCharging: Boolean - get() = when (areValuesFlipped) { - false -> rawFlags.isBitSet(1) - true -> rawFlags.isBitSet(0) - } - - private val isThisPodInThecase: Boolean - get() = rawStatus.isBitSet(6) - - override val isLeftPodInEar: Boolean - get() = when (areValuesFlipped xor isThisPodInThecase) { - true -> rawStatus.isBitSet(3) - false -> rawStatus.isBitSet(1) - } - - override val isRightPodInEar: Boolean - get() = when (areValuesFlipped xor isThisPodInThecase) { - true -> rawStatus.isBitSet(1) - false -> rawStatus.isBitSet(3) - } + get() = rssiAverage ?: super.rssi override val batteryCasePercent: Float? - get() = when (val value = rawCaseBattery.toInt()) { - 15 -> cachedBatteryPercentage - else -> if (value > 10) { - log { "Case: Above 100% battery: $value" } - 1.0f - } else { - value / 10f - } - } - - override val isCaseCharging: Boolean - get() = rawFlags.isBitSet(2) + get() = super.batteryCasePercent ?: cachedBatteryPercentage class Factory @Inject constructor() : ApplePodsFactory(TAG) { - override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + override fun isResponsible(message: ProximityMessage): Boolean = message.run { // Official message length is 19HEX, i.e. binary 25, did they copy this wrong? getModelInfo().full == DEVICE_CODE && length == 19 } override fun create( scanResult: BleScanResult, - message: ProximityPairing.Message, - decrypted: UByteArray? + payload: ProximityPayload ): ApplePods { - var basic = FakeAirPodsGen3(scanResult = scanResult, proximityMessage = message) + var basic = FakeAirPodsGen3(scanResult = scanResult, payload = payload) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro.kt index 06332a45..9f8d79b0 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro.kt @@ -3,15 +3,16 @@ package eu.darken.capod.pods.core.apple.misc import androidx.annotation.DrawableRes import eu.darken.capod.common.R import eu.darken.capod.common.bluetooth.BleScanResult -import eu.darken.capod.common.debug.logging.log import eu.darken.capod.common.debug.logging.logTag -import eu.darken.capod.common.isBitSet -import eu.darken.capod.common.lowerNibble -import eu.darken.capod.common.upperNibble -import eu.darken.capod.pods.core.* +import eu.darken.capod.pods.core.HasCase +import eu.darken.capod.pods.core.HasChargeDetectionDual +import eu.darken.capod.pods.core.HasEarDetectionDual +import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory -import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.DualApplePods +import eu.darken.capod.pods.core.apple.protocol.ProximityMessage +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant import javax.inject.Inject @@ -20,17 +21,17 @@ import javax.inject.Inject * Shorter data structure. * https://discord.com/channels/548521543039189022/927235844127993866/962068944196358234 */ -data class FakeAirPodsPro constructor( +data class FakeAirPodsPro( 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 payload: ProximityPayload, override val reliability: Float = PodDevice.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, -) : ApplePods, DualPodDevice, HasChargeDetectionDual, HasEarDetectionDual, HasCase { +) : DualApplePods, HasChargeDetectionDual, HasEarDetectionDual, HasCase { override val model: PodDevice.Model = PodDevice.Model.FAKE_AIRPODS_PRO @@ -51,103 +52,23 @@ data class FakeAirPodsPro constructor( get() = R.drawable.devic_airpods_pro2_right override val rssi: Int - get() = rssiAverage ?: super.rssi - - /** - * Normally values for the left pod are in the lower nibbles, if the left pod is primary (microphone) - * If the right pod is the primary, the values are flipped. - */ - val areValuesFlipped: Boolean - get() = !rawStatus.isBitSet(5) - - override val batteryLeftPodPercent: Float? - get() { - val value = when (areValuesFlipped) { - true -> rawPodsBattery.upperNibble.toInt() - false -> rawPodsBattery.lowerNibble.toInt() - } - return when (value) { - 15 -> null - else -> if (value > 10) { - log { "Left pod: Above 100% battery: $value" } - 1.0f - } else { - (value / 10f) - } - } - } - - override val batteryRightPodPercent: Float? - get() { - val value = when (areValuesFlipped) { - true -> rawPodsBattery.lowerNibble.toInt() - false -> rawPodsBattery.upperNibble.toInt() - } - return when (value) { - 15 -> null - else -> if (value > 10) { - log { "Right pod: Above 100% battery: $value" } - 1.0f - } else { - value / 10f - } - } - } - - override val isLeftPodCharging: Boolean - get() = when (areValuesFlipped) { - false -> rawFlags.isBitSet(0) - true -> rawFlags.isBitSet(1) - } - - override val isRightPodCharging: Boolean - get() = when (areValuesFlipped) { - false -> rawFlags.isBitSet(1) - true -> rawFlags.isBitSet(0) - } - - private val isThisPodInThecase: Boolean - get() = rawStatus.isBitSet(6) - - override val isLeftPodInEar: Boolean - get() = when (areValuesFlipped xor isThisPodInThecase) { - true -> rawStatus.isBitSet(3) - false -> rawStatus.isBitSet(1) - } - - override val isRightPodInEar: Boolean - get() = when (areValuesFlipped xor isThisPodInThecase) { - true -> rawStatus.isBitSet(1) - false -> rawStatus.isBitSet(3) - } + get() = rssiAverage ?: super.rssi override val batteryCasePercent: Float? - get() = when (val value = rawCaseBattery.toInt()) { - 15 -> cachedBatteryPercentage - else -> if (value > 10) { - log { "Case: Above 100% battery: $value" } - 1.0f - } else { - value / 10f - } - } - - override val isCaseCharging: Boolean - get() = rawFlags.isBitSet(2) + get() = super.batteryCasePercent ?: cachedBatteryPercentage class Factory @Inject constructor() : ApplePodsFactory(TAG) { - override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + override fun isResponsible(message: ProximityMessage): Boolean = message.run { // Official message length is 19HEX, i.e. binary 25, did they copy this wrong? getModelInfo().full == DEVICE_CODE && length == 19 } override fun create( scanResult: BleScanResult, - message: ProximityPairing.Message, - decrypted: UByteArray? + payload: ProximityPayload ): ApplePods { - var basic = FakeAirPodsPro(scanResult = scanResult, proximityMessage = message) + var basic = FakeAirPodsPro(scanResult = scanResult, payload = payload) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro2.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro2.kt index 601110ab..61600472 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro2.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro2.kt @@ -3,16 +3,18 @@ package eu.darken.capod.pods.core.apple.misc import androidx.annotation.DrawableRes import eu.darken.capod.common.R import eu.darken.capod.common.bluetooth.BleScanResult -import eu.darken.capod.common.debug.logging.log import eu.darken.capod.common.debug.logging.logTag -import eu.darken.capod.common.isBitSet -import eu.darken.capod.common.lowerNibble -import eu.darken.capod.common.upperNibble -import eu.darken.capod.pods.core.* +import eu.darken.capod.pods.core.DualPodDevice +import eu.darken.capod.pods.core.HasCase +import eu.darken.capod.pods.core.HasChargeDetectionDual +import eu.darken.capod.pods.core.HasEarDetectionDual +import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory import eu.darken.capod.pods.core.apple.DualApplePods -import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.DualApplePods.LidState +import eu.darken.capod.pods.core.apple.protocol.ProximityMessage +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant import javax.inject.Inject @@ -20,18 +22,18 @@ import javax.inject.Inject * AirPods Pro2 clone * https://discord.com/channels/548521543039189022/927235844127993866/1060911213539774504 */ -data class FakeAirPodsPro2 constructor( +data class FakeAirPodsPro2( 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 payload: ProximityPayload, override val reliability: Float = PodDevice.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, - private val cachedCaseState: DualApplePods.LidState? = null, -) : ApplePods, HasChargeDetectionDual, DualPodDevice, HasEarDetectionDual, HasCase { + private val cachedCaseState: LidState? = null, +) : DualApplePods, HasChargeDetectionDual, DualPodDevice, HasEarDetectionDual, HasCase { override val model: PodDevice.Model = PodDevice.Model.FAKE_AIRPODS_PRO2 @@ -52,99 +54,25 @@ data class FakeAirPodsPro2 constructor( get() = R.drawable.devic_airpods_pro2_right override val rssi: Int - get() = rssiAverage ?: scanResult.rssi - - private val areValuesFlipped: Boolean - get() = !rawStatus.isBitSet(5) - - override val batteryLeftPodPercent: Float? - get() { - val value = when (areValuesFlipped) { - true -> rawPodsBattery.upperNibble.toInt() - false -> rawPodsBattery.lowerNibble.toInt() - } - return when (value) { - 15 -> null - else -> if (value > 10) { - log { "Left pod: Above 100% battery: $value" } - 1.0f - } else { - (value / 10f) - } - } - } - - override val batteryRightPodPercent: Float? - get() { - val value = when (areValuesFlipped) { - true -> rawPodsBattery.lowerNibble.toInt() - false -> rawPodsBattery.upperNibble.toInt() - } - return when (value) { - 15 -> null - else -> if (value > 10) { - log { "Right pod: Above 100% battery: $value" } - 1.0f - } else { - value / 10f - } - } - } - - private val isThisPodInThecase: Boolean - get() = rawStatus.isBitSet(6) - - override val isLeftPodInEar: Boolean - get() = when (areValuesFlipped xor isThisPodInThecase) { - true -> rawStatus.isBitSet(3) - false -> rawStatus.isBitSet(1) - } - - override val isRightPodInEar: Boolean - get() = when (areValuesFlipped xor isThisPodInThecase) { - true -> rawStatus.isBitSet(1) - false -> rawStatus.isBitSet(3) - } - - override val isLeftPodCharging: Boolean - get() = when (areValuesFlipped) { - false -> rawFlags.isBitSet(0) - true -> rawFlags.isBitSet(1) - } - - override val isRightPodCharging: Boolean - get() = when (areValuesFlipped) { - false -> rawFlags.isBitSet(1) - true -> rawFlags.isBitSet(0) - } + get() = rssiAverage ?: super.rssi + override val caseLidState: LidState + get() = cachedCaseState ?: super.caseLidState override val batteryCasePercent: Float? - get() = when (val value = rawCaseBattery.toInt()) { - 15 -> cachedBatteryPercentage - else -> if (value > 10) { - log { "Case: Above 100% battery: $value" } - 1.0f - } else { - value / 10f - } - } - - override val isCaseCharging: Boolean - get() = rawFlags.isBitSet(2) + get() = super.batteryCasePercent ?: cachedBatteryPercentage class Factory @Inject constructor() : ApplePodsFactory(TAG) { - override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + override fun isResponsible(message: ProximityMessage): Boolean = message.run { // Official message length is 19HEX, i.e. binary 25, did they copy this wrong? getModelInfo().full == DEVICE_CODE && length == 19 } override fun create( scanResult: BleScanResult, - message: ProximityPairing.Message, - decrypted: UByteArray? + payload: ProximityPayload ): ApplePods { - var basic = FakeAirPodsPro2(scanResult = scanResult, proximityMessage = message) + var basic = FakeAirPodsPro2(scanResult = scanResult, payload = payload) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/UnknownAppleDevice.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/UnknownAppleDevice.kt index b58a2d49..2cd3824b 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/UnknownAppleDevice.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/UnknownAppleDevice.kt @@ -7,7 +7,8 @@ import eu.darken.capod.common.debug.logging.logTag import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory -import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.pods.core.apple.protocol.ProximityMessage +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant import javax.inject.Inject @@ -17,7 +18,7 @@ data class UnknownAppleDevice( override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, - override val proximityMessage: ProximityPairing.Message, + override val payload: ProximityPayload, override val reliability: Float = 0f, private val rssiAverage: Int? = null, ) : ApplePods { @@ -30,14 +31,13 @@ data class UnknownAppleDevice( get() = rssiAverage ?: super.rssi class Factory @Inject constructor() : ApplePodsFactory(TAG) { - override fun isResponsible(message: ProximityPairing.Message): Boolean = true + override fun isResponsible(message: ProximityMessage): Boolean = true override fun create( scanResult: BleScanResult, - message: ProximityPairing.Message, - decrypted: UByteArray?, + payload: ProximityPayload, ): ApplePods { - var basic = UnknownAppleDevice(scanResult = scanResult, proximityMessage = message) + var basic = UnknownAppleDevice(scanResult = scanResult, payload = payload) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/protocol/MessageDecrypter.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/protocol/MessageDecrypter.kt deleted file mode 100644 index 88adb82d..00000000 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/protocol/MessageDecrypter.kt +++ /dev/null @@ -1,44 +0,0 @@ -package eu.darken.capod.pods.core.apple.protocol - -import android.annotation.SuppressLint -import eu.darken.capod.common.debug.logging.Logging -import eu.darken.capod.common.debug.logging.asLog -import eu.darken.capod.common.debug.logging.log -import eu.darken.capod.common.debug.logging.logTag -import okio.ByteString.Companion.toByteString -import javax.crypto.Cipher -import javax.crypto.spec.SecretKeySpec -import javax.inject.Inject - -class MessageDecrypter @Inject constructor() { - - @SuppressLint("GetInstance") - fun decrypt(message: ProximityPairing.Message, key: ByteArray): UByteArray? { - val data = message.data.toByteArray() - if (data.size < 16) return null - - val decryptedData = try { - val cipher = Cipher.getInstance("AES/ECB/NoPadding").apply { - val secretKey = SecretKeySpec(key, "AES") - init(Cipher.DECRYPT_MODE, secretKey) - } - cipher.doFinal(data.copyOfRange(data.size - 16, data.size)) - } catch (e: Exception) { - log(TAG, Logging.Priority.ERROR) { "Failed to decrypt $message with ${key.toByteString()}\n${e.asLog()}" } - null - } - - log( - TAG, - Logging.Priority.VERBOSE - ) { "Decrypted $message with ${key.toByteString()} to ${decryptedData?.toByteString()}" } - - if (decryptedData == null || decryptedData.size != 16) return null - - return decryptedData.toUByteArray() - } - - companion object { - private val TAG = logTag("Pod", "Factory", "Decrypter") - } -} \ No newline at end of file diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/protocol/ProximityMessage.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/protocol/ProximityMessage.kt new file mode 100644 index 00000000..a59ebcba --- /dev/null +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/protocol/ProximityMessage.kt @@ -0,0 +1,58 @@ +package eu.darken.capod.pods.core.apple.protocol + +import android.R.id.message +import android.annotation.SuppressLint +import dagger.Reusable +import eu.darken.capod.common.debug.logging.Logging +import eu.darken.capod.common.debug.logging.asLog +import eu.darken.capod.common.debug.logging.log +import eu.darken.capod.common.debug.logging.logTag +import okio.ByteString.Companion.toByteString +import javax.crypto.Cipher +import javax.crypto.spec.SecretKeySpec +import javax.inject.Inject + +data class ProximityMessage( + val type: UByte, + val length: Int, + val data: UByteArray +) { + + override fun toString(): String { + val dataHex = data.joinToString(separator = " ") { String.format("%02X", it.toByte()) } + return "ProximityPairing.Message(type=$type, length=$length, data=$dataHex)" + } + + @Reusable + class Decrypter @Inject constructor() { + @SuppressLint("GetInstance") + fun decrypt(data: ByteArray, key: ByteArray): UByteArray? { + val decryptedData = try { + val cipher = Cipher.getInstance("AES/ECB/NoPadding").apply { + val secretKey = SecretKeySpec(key, "AES") + init(Cipher.DECRYPT_MODE, secretKey) + } + cipher.doFinal(data.copyOfRange(data.size - 16, data.size)) + } catch (e: Exception) { + log( + TAG, + Logging.Priority.ERROR + ) { "Failed to decrypt $message with ${key.toByteString()}\n${e.asLog()}" } + null + } + + log( + TAG, + Logging.Priority.VERBOSE + ) { "Decrypted $message with ${key.toByteString()} to ${decryptedData?.toByteString()}" } + + if (decryptedData == null || decryptedData.size != 16) return null + + return decryptedData.toUByteArray() + } + + companion object { + private val TAG = logTag("Pod", "Factory", "Decrypter") + } + } +} \ No newline at end of file diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/protocol/ProximityPairing.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/protocol/ProximityPairing.kt index bb9ca801..758d808f 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/protocol/ProximityPairing.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/protocol/ProximityPairing.kt @@ -8,27 +8,15 @@ import javax.inject.Inject object ProximityPairing { - data class Message( - val type: UByte, - val length: Int, - val data: UByteArray - ) { - - override fun toString(): String { - val dataHex = data.joinToString(separator = " ") { String.format("%02X", it.toByte()) } - return "ProximityPairing.Message(type=$type, length=$length, data=$dataHex)" - } - } - @Reusable class Decoder @Inject constructor() { - fun decode(message: ContinuityProtocol.Message): Message? { + fun decode(message: ContinuityProtocol.Message): ProximityMessage? { if (message.type != CONTINUITY_PROTOCOL_MESSAGE_TYPE_PROXIMITY_PAIRING) { log { "Not a proximity pairing message: $this" } return null } - return Message( + return ProximityMessage( type = message.type, length = message.length, data = message.data diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/protocol/ProximityPayload.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/protocol/ProximityPayload.kt new file mode 100644 index 00000000..54c2f824 --- /dev/null +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/protocol/ProximityPayload.kt @@ -0,0 +1,15 @@ +package eu.darken.capod.pods.core.apple.protocol + +data class ProximityPayload( + val public: Public, + val private: Private?, +) { + + data class Public( + val data: UByteArray, + ) + + data class Private( + val data: UByteArray, + ) +} diff --git a/app-common/src/main/java/eu/darken/capod/reaction/core/popup/PopUpReaction.kt b/app-common/src/main/java/eu/darken/capod/reaction/core/popup/PopUpReaction.kt index 7b6c6c94..c0ae14bb 100644 --- a/app-common/src/main/java/eu/darken/capod/reaction/core/popup/PopUpReaction.kt +++ b/app-common/src/main/java/eu/darken/capod/reaction/core/popup/PopUpReaction.kt @@ -51,8 +51,8 @@ class PopUpReaction @Inject constructor( return@mapNotNull null } log(TAG, VERBOSE) { - val prev = previous?.rawCaseLidState?.let { String.format("%02X", it.toByte()) } - val cur = current.rawCaseLidState.let { String.format("%02X", it.toByte()) } + val prev = previous?.pubCaseLidState?.let { String.format("%02X", it.toByte()) } + val cur = current.pubCaseLidState.let { String.format("%02X", it.toByte()) } "previous=$prev (${previous?.caseLidState}), current=$cur (${current.caseLidState})" } log(TAG, VERBOSE) { "previous-id=${previous?.identifier}, current-id=${current.identifier}" } diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/BaseAirPodsTest.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/BaseAirPodsTest.kt index 4d687012..24c5981d 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/BaseAirPodsTest.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/BaseAirPodsTest.kt @@ -1,28 +1,35 @@ package eu.darken.capod.pods.core.apple +import dagger.BindsInstance import dagger.Component import eu.darken.capod.common.SystemClockWrap import eu.darken.capod.common.bluetooth.BleScanResult +import eu.darken.capod.common.serialization.SerializationModule +import eu.darken.capod.main.core.GeneralSettings import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.apple.protocol.ContinuityProtocol import io.mockk.MockKAnnotations import io.mockk.every +import io.mockk.mockk import io.mockk.mockkObject import org.junit.jupiter.api.BeforeEach import testhelpers.BaseTest +import testhelpers.preferences.mockFlowPreference import java.time.Instant import javax.inject.Singleton abstract class BaseAirPodsTest : BaseTest() { @Singleton - @Component(modules = [AppleFactoryModule::class]) + @Component(modules = [AppleFactoryModule::class, SerializationModule::class]) interface AppleFactoryTestComponent { val appleFactory: AppleFactory @Component.Factory interface Factory { - fun create(): AppleFactoryTestComponent + fun create( + @BindsInstance generalSettings: GeneralSettings, + ): AppleFactoryTestComponent } } @@ -34,7 +41,14 @@ abstract class BaseAirPodsTest : BaseTest() { manufacturerSpecificData = emptyMap() ) - val factory: AppleFactory = DaggerBaseAirPodsTest_AppleFactoryTestComponent.factory().create().appleFactory + val generalSettings = mockk().apply { + every { mainDeviceIdentityKey } returns mockFlowPreference(null) + every { mainDeviceEncryptionKey } returns mockFlowPreference(null) + } + + val factory: AppleFactory = DaggerBaseAirPodsTest_AppleFactoryTestComponent.factory().create( + generalSettings = generalSettings + ).appleFactory @BeforeEach fun setup() { diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/BasicSingleApplePodsTest.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/BasicSingleApplePodsTest.kt index 415f71a7..7781f00f 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/BasicSingleApplePodsTest.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/BasicSingleApplePodsTest.kt @@ -9,15 +9,15 @@ class BasicSingleApplePodsTest : BaseAirPodsTest() { @Test fun `test mapping`() = runTest { create("07 19 01 05 20 00 F5 0F 01 01 00 6D CE C0 04 22 0A 85 31 2D 82 6B 42 80 01 20 1A") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x0520.toUShort() - rawStatus shouldBe 0x00.toUByte() - rawPodsBattery shouldBe 0xF5.toUByte() - rawFlags shouldBe 0x0.toUShort() - rawCaseBattery shouldBe 0xF.toUShort() - rawCaseLidState shouldBe 0x01.toUByte() - rawDeviceColor shouldBe 0x01.toUByte() - rawSuffix shouldBe 0x00.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x0520.toUShort() + pubStatus shouldBe 0x00.toUByte() + pubPodsBattery shouldBe 0xF5.toUByte() + pubFlags shouldBe 0x0.toUShort() + pubCaseBattery shouldBe 0xF.toUShort() + pubCaseLidState shouldBe 0x01.toUByte() + pubDeviceColor shouldBe 0x01.toUByte() + pubSuffix shouldBe 0x00.toUByte() } } diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/DualApplePodsTest.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/DualApplePodsTest.kt index 25570ba6..f1cccc9e 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/DualApplePodsTest.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/DualApplePodsTest.kt @@ -11,15 +11,15 @@ class DualApplePodsTest : BaseAirPodsTest() { fun `test bit mapping`() = runTest { create("07 19 01 0E 20 54 AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x0e20.toUShort() - rawStatus shouldBe 0x54.toUByte() - rawPodsBattery shouldBe 0xAA.toUByte() - rawFlags shouldBe 0xB.toUShort() - rawCaseBattery shouldBe 0x5.toUShort() - rawCaseLidState shouldBe 0x31.toUByte() - rawDeviceColor shouldBe 0x00.toUByte() - rawSuffix shouldBe 0x00.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x0e20.toUShort() + pubStatus shouldBe 0x54.toUByte() + pubPodsBattery shouldBe 0xAA.toUByte() + pubFlags shouldBe 0xB.toUShort() + pubCaseBattery shouldBe 0x5.toUShort() + pubCaseLidState shouldBe 0x31.toUByte() + pubDeviceColor shouldBe 0x00.toUByte() + pubSuffix shouldBe 0x00.toUByte() } } diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/SingleApplePodsTest.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/SingleApplePodsTest.kt index ab3965a1..a4216ffa 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/SingleApplePodsTest.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/SingleApplePodsTest.kt @@ -9,15 +9,15 @@ class SingleApplePodsTest : BaseAirPodsTest() { @Test fun `default bit mapping Max`() = runTest { create("07 19 01 0A 20 62 04 80 01 0F 40 0D 70 50 16 F2 40 83 16 BF 10 16 34 9B 74 84 E8") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x0A20.toUShort() - rawStatus shouldBe 0x62.toUByte() - rawPodsBattery shouldBe 0x04.toUByte() - rawFlags shouldBe 0x8.toUShort() - rawCaseBattery shouldBe 0x0.toUShort() - rawCaseLidState shouldBe 0x01.toUByte() - rawDeviceColor shouldBe 0x0F.toUByte() - rawSuffix shouldBe 0x40.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x0A20.toUShort() + pubStatus shouldBe 0x62.toUByte() + pubPodsBattery shouldBe 0x04.toUByte() + pubFlags shouldBe 0x8.toUShort() + pubCaseBattery shouldBe 0x0.toUShort() + pubCaseLidState shouldBe 0x01.toUByte() + pubDeviceColor shouldBe 0x0F.toUByte() + pubSuffix shouldBe 0x40.toUByte() } } } \ No newline at end of file diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen1Test.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen1Test.kt index d1cdf035..9992cecc 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen1Test.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen1Test.kt @@ -14,15 +14,15 @@ class AirPodsGen1Test : BaseAirPodsTest() { @Test fun `fake airpods`() = runTest { create("07 19 01 02 20 55 AF 56 31 00 00 6F E4 DF 10 AF 10 60 81 03 3B 76 D9 C7 11 22 88") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x0220.toUShort() - rawStatus shouldBe 0x55.toUByte() - rawPodsBattery shouldBe 0xAF.toUByte() - rawFlags shouldBe 0x5.toUShort() - rawCaseBattery shouldBe 0x6.toUShort() - rawCaseLidState shouldBe 0x31.toUByte() - rawDeviceColor shouldBe 0x00.toUByte() - rawSuffix shouldBe 0x00.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x0220.toUShort() + pubStatus shouldBe 0x55.toUByte() + pubPodsBattery shouldBe 0xAF.toUByte() + pubFlags shouldBe 0x5.toUShort() + pubCaseBattery shouldBe 0x6.toUShort() + pubCaseLidState shouldBe 0x31.toUByte() + pubDeviceColor shouldBe 0x00.toUByte() + pubSuffix shouldBe 0x00.toUByte() batteryLeftPodPercent shouldBe 1.0f batteryRightPodPercent shouldBe null diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen2Test.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen2Test.kt index fac830a4..58db3694 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen2Test.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen2Test.kt @@ -13,15 +13,15 @@ class AirPodsGen2Test : BaseAirPodsTest() { @Test fun `random Neighbor AirPodsGen2`() = runTest { create("07 19 01 0F 20 02 F9 8F 01 00 05 F2 7E 14 E0 54 0A 53 69 5B 7D F2 15 1F D7 B1 12") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x0F20.toUShort() - rawStatus shouldBe 0x02.toUByte() - rawPodsBattery shouldBe 0xF9.toUByte() - rawFlags shouldBe 0x8.toUShort() - rawCaseBattery shouldBe 0xF.toUShort() - rawCaseLidState shouldBe 0x01.toUByte() - rawDeviceColor shouldBe 0x00.toUByte() - rawSuffix shouldBe 0x05.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x0F20.toUShort() + pubStatus shouldBe 0x02.toUByte() + pubPodsBattery shouldBe 0xF9.toUByte() + pubFlags shouldBe 0x8.toUShort() + pubCaseBattery shouldBe 0xF.toUShort() + pubCaseLidState shouldBe 0x01.toUByte() + pubDeviceColor shouldBe 0x00.toUByte() + pubSuffix shouldBe 0x05.toUByte() batteryLeftPodPercent shouldBe null batteryRightPodPercent shouldBe 0.9f diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen3Test.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen3Test.kt index 95a80bab..8bef8869 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen3Test.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen3Test.kt @@ -13,15 +13,15 @@ class AirPodsGen3Test : BaseAirPodsTest() { @Test fun `AirPods Gen3`() = runTest { create("07 19 01 13 20 75 AA B9 31 00 04 67 9A 57 DF BE F6 90 52 B0 04 1F 8D 89 DA F4 9E") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x1320.toUShort() - rawStatus shouldBe 0x75.toUByte() - rawPodsBattery shouldBe 0xAA.toUByte() - rawFlags shouldBe 0xB.toUShort() - rawCaseBattery shouldBe 0x9.toUShort() - rawCaseLidState shouldBe 0x31.toUByte() - rawDeviceColor shouldBe 0x00.toUByte() - rawSuffix shouldBe 0x04.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x1320.toUShort() + pubStatus shouldBe 0x75.toUByte() + pubPodsBattery shouldBe 0xAA.toUByte() + pubFlags shouldBe 0xB.toUShort() + pubCaseBattery shouldBe 0x9.toUShort() + pubCaseLidState shouldBe 0x31.toUByte() + pubDeviceColor shouldBe 0x00.toUByte() + pubSuffix shouldBe 0x04.toUByte() batteryLeftPodPercent shouldBe 1.0f batteryRightPodPercent shouldBe 1.0f diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4AncTest.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4AncTest.kt index 6a984c84..ba75c87f 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4AncTest.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4AncTest.kt @@ -13,15 +13,15 @@ class AirPodsGen4AncTest : BaseAirPodsTest() { @Test fun `AirPods Gen4 with ANC via log from #226`() = runTest { create("07 19 01 1B 20 0B 9A 8F 10 00 04 43 DF EC 1D D3 F1 C3 F4 A1 9B 29 26 B9 E7 3A A0") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x1B20.toUShort() - rawStatus shouldBe 0x0b.toUByte() - rawPodsBattery shouldBe 0x9A.toUByte() - rawFlags shouldBe 0x8.toUShort() - rawCaseBattery shouldBe 0xF.toUShort() - rawCaseLidState shouldBe 0x10.toUByte() - rawDeviceColor shouldBe 0x00.toUByte() - rawSuffix shouldBe 0x04.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x1B20.toUShort() + pubStatus shouldBe 0x0b.toUByte() + pubPodsBattery shouldBe 0x9A.toUByte() + pubFlags shouldBe 0x8.toUShort() + pubCaseBattery shouldBe 0xF.toUShort() + pubCaseLidState shouldBe 0x10.toUByte() + pubDeviceColor shouldBe 0x00.toUByte() + pubSuffix shouldBe 0x04.toUByte() batteryLeftPodPercent shouldBe 0.9f batteryRightPodPercent shouldBe 1.0f diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4Test.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4Test.kt index f12cf37e..369f928a 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4Test.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4Test.kt @@ -13,15 +13,15 @@ class AirPodsGen4Test : BaseAirPodsTest() { @Test fun `AirPods Gen4 via log from #225`() = runTest { create("07 19 01 19 20 2B 33 8F 11 00 04 59 D4 57 20 0F 1C 13 38 B2 00 74 E9 DD 70 D7 A5") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x1920.toUShort() - rawStatus shouldBe 0x2B.toUByte() - rawPodsBattery shouldBe 0x33.toUByte() - rawFlags shouldBe 0x8.toUShort() - rawCaseBattery shouldBe 0xF.toUShort() - rawCaseLidState shouldBe 0x11.toUByte() - rawDeviceColor shouldBe 0x00.toUByte() - rawSuffix shouldBe 0x04.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x1920.toUShort() + pubStatus shouldBe 0x2B.toUByte() + pubPodsBattery shouldBe 0x33.toUByte() + pubFlags shouldBe 0x8.toUShort() + pubCaseBattery shouldBe 0xF.toUShort() + pubCaseLidState shouldBe 0x11.toUByte() + pubDeviceColor shouldBe 0x00.toUByte() + pubSuffix shouldBe 0x04.toUByte() batteryLeftPodPercent shouldBe 0.3f batteryRightPodPercent shouldBe 0.3f diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMaxTest.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMaxTest.kt index 62234e92..8d1135b6 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMaxTest.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMaxTest.kt @@ -14,15 +14,15 @@ class AirPodsMaxTest : BaseAirPodsTest() { @Test fun `default AirPods Max`() = runTest { create("07 19 01 0A 20 62 04 80 01 0F 40 0D 70 50 16 F2 40 83 16 BF 10 16 34 9B 74 84 E8") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x0A20.toUShort() - rawStatus shouldBe 0x62.toUByte() - rawPodsBattery shouldBe 0x04.toUByte() - rawFlags shouldBe 0x8.toUShort() - rawCaseBattery shouldBe 0x0.toUShort() - rawCaseLidState shouldBe 0x01.toUByte() - rawDeviceColor shouldBe 0x0F.toUByte() - rawSuffix shouldBe 0x40.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x0A20.toUShort() + pubStatus shouldBe 0x62.toUByte() + pubPodsBattery shouldBe 0x04.toUByte() + pubFlags shouldBe 0x8.toUShort() + pubCaseBattery shouldBe 0x0.toUShort() + pubCaseLidState shouldBe 0x01.toUByte() + pubDeviceColor shouldBe 0x0F.toUByte() + pubSuffix shouldBe 0x40.toUByte() batteryHeadsetPercent shouldBe 0.4f @@ -36,15 +36,15 @@ class AirPodsMaxTest : BaseAirPodsTest() { @Test fun `default AirPods Max flipped values`() = runTest { create("07 19 01 0A 20 02 05 80 04 0F 44 A7 60 9B F8 3C FD B1 D8 1C 61 EA 82 60 A3 2C 4E") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x0A20.toUShort() - rawStatus shouldBe 0x02.toUByte() - rawPodsBattery shouldBe 0x05.toUByte() - rawFlags shouldBe 0x8.toUShort() - rawCaseBattery shouldBe 0x0.toUShort() - rawCaseLidState shouldBe 0x04.toUByte() - rawDeviceColor shouldBe 0x0F.toUByte() - rawSuffix shouldBe 0x44.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x0A20.toUShort() + pubStatus shouldBe 0x02.toUByte() + pubPodsBattery shouldBe 0x05.toUByte() + pubFlags shouldBe 0x8.toUShort() + pubCaseBattery shouldBe 0x0.toUShort() + pubCaseLidState shouldBe 0x04.toUByte() + pubDeviceColor shouldBe 0x0F.toUByte() + pubSuffix shouldBe 0x44.toUByte() batteryHeadsetPercent shouldBe 0.5f @@ -55,21 +55,21 @@ class AirPodsMaxTest : BaseAirPodsTest() { @Test fun `some dude at the gym`() = runTest { create("07 19 01 0A 20 23 07 80 03 03 65 1F 28 32 D0 D9 71 43 00 9A 40 E7 6B EA 6C 2C FB") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x0A20.toUShort() - rawStatus shouldBe 0x23.toUByte() - rawPodsBattery shouldBe 0x07.toUByte() - rawFlags shouldBe 0x8.toUShort() - rawCaseBattery shouldBe 0x0.toUShort() - rawCaseLidState shouldBe 0x03.toUByte() - rawDeviceColor shouldBe 0x03.toUByte() - rawSuffix shouldBe 0x65.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x0A20.toUShort() + pubStatus shouldBe 0x23.toUByte() + pubPodsBattery shouldBe 0x07.toUByte() + pubFlags shouldBe 0x8.toUShort() + pubCaseBattery shouldBe 0x0.toUShort() + pubCaseLidState shouldBe 0x03.toUByte() + pubDeviceColor shouldBe 0x03.toUByte() + pubSuffix shouldBe 0x65.toUByte() batteryHeadsetPercent shouldBe 0.7f isHeadsetBeingCharged shouldBe false - rawStatus.isBitSet(5) shouldBe true + pubStatus.isBitSet(5) shouldBe true podStyle shouldBe HasAppleColor.DeviceColor.BLUE } @@ -78,15 +78,15 @@ class AirPodsMaxTest : BaseAirPodsTest() { @Test fun `wear status`() = runTest { create("07 19 01 0A 20 03 07 80 03 03 65 1F 28 32 D0 D9 71 43 00 9A 40 E7 6B EA 6C 2C FB") { - rawStatus shouldBe 0x03.toUByte() + pubStatus shouldBe 0x03.toUByte() - rawStatus.isBitSet(5) shouldBe false + pubStatus.isBitSet(5) shouldBe false isBeingWorn shouldBe false } create("07 19 01 0A 20 23 07 80 03 03 65 1F 28 32 D0 D9 71 43 00 9A 40 E7 6B EA 6C 2C FB") { - rawStatus shouldBe 0x23.toUByte() + pubStatus shouldBe 0x23.toUByte() - rawStatus.isBitSet(5) shouldBe true + pubStatus.isBitSet(5) shouldBe true isBeingWorn shouldBe true } } diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMaxUsbcTest.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMaxUsbcTest.kt index 6d93192d..552478fb 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMaxUsbcTest.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMaxUsbcTest.kt @@ -12,15 +12,15 @@ class AirPodsMaxUsbcTest : BaseAirPodsTest() { @Test fun `default AirPods Max`() = runTest { create("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() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x1F20.toUShort() + pubStatus shouldBe 0x2B.toUByte() + pubPodsBattery shouldBe 0x05.toUByte() + pubFlags shouldBe 0x8.toUShort() + pubCaseBattery shouldBe 0x0.toUShort() + pubCaseLidState shouldBe 0x03.toUByte() + pubDeviceColor shouldBe 0x12.toUByte() + pubSuffix shouldBe 0xC5.toUByte() batteryHeadsetPercent shouldBe 0.5f diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2Test.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2Test.kt index 03600f07..8b0ad012 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2Test.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2Test.kt @@ -16,15 +16,15 @@ class AirPodsPro2Test : BaseAirPodsTest() { fun `test AirPods Pro 2 - unknown setup from #31`() = runTest { create("07 19 01 14 20 55 88 F9 51 00 04 20 50 03 CA D5 C9 AC 0F FA 84 78 94 5A 4D DF F5") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x1420.toUShort() - rawStatus shouldBe 0x55.toUByte() - rawPodsBattery shouldBe 0x88.toUByte() - rawFlags shouldBe 0xF.toUShort() - rawCaseBattery shouldBe 0x9.toUShort() - rawCaseLidState shouldBe 0x51.toUByte() - rawDeviceColor shouldBe 0x0.toUByte() - rawSuffix shouldBe 0x04.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x1420.toUShort() + pubStatus shouldBe 0x55.toUByte() + pubPodsBattery shouldBe 0x88.toUByte() + pubFlags shouldBe 0xF.toUShort() + pubCaseBattery shouldBe 0x9.toUShort() + pubCaseLidState shouldBe 0x51.toUByte() + pubDeviceColor shouldBe 0x0.toUByte() + pubSuffix shouldBe 0x04.toUByte() isLeftPodMicrophone shouldBe true isRightPodMicrophone shouldBe false @@ -53,15 +53,15 @@ class AirPodsPro2Test : BaseAirPodsTest() { fun `test AirPods Pro 2 - unknown setup from reddit user`() = runTest { create("07 19 01 14 20 2B 9A 8F 01 00 04 0F 26 1A C4 2B FA 2F B9 B6 08 CD 60 CB DF 75 AB") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x1420.toUShort() - rawStatus shouldBe 0x2B.toUByte() - rawPodsBattery shouldBe 0x9A.toUByte() - rawFlags shouldBe 0x8.toUShort() - rawCaseBattery shouldBe 0xF.toUShort() - rawCaseLidState shouldBe 0x01.toUByte() - rawDeviceColor shouldBe 0x0.toUByte() - rawSuffix shouldBe 0x04.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x1420.toUShort() + pubStatus shouldBe 0x2B.toUByte() + pubPodsBattery shouldBe 0x9A.toUByte() + pubFlags shouldBe 0x8.toUShort() + pubCaseBattery shouldBe 0xF.toUShort() + pubCaseLidState shouldBe 0x01.toUByte() + pubDeviceColor shouldBe 0x0.toUByte() + pubSuffix shouldBe 0x04.toUByte() isLeftPodMicrophone shouldBe true isRightPodMicrophone shouldBe false diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2UsbcTest.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2UsbcTest.kt index 3c8f7c5f..94b7e883 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2UsbcTest.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2UsbcTest.kt @@ -16,15 +16,15 @@ class AirPodsPro2UsbcTest : BaseAirPodsTest() { fun `AirPods Pro 2 with USB-C - via #164`() = runTest { create("07 19 01 24 20 0B 99 8F 11 00 04 BD A7 3B FF 2D 8A 3C AF 9B 1A 7C 74 B7 A9 D1 C3") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x2420.toUShort() - rawStatus shouldBe 0x0B.toUByte() - rawPodsBattery shouldBe 0x99.toUByte() - rawFlags shouldBe 0x8.toUShort() - rawCaseBattery shouldBe 0xF.toUShort() - rawCaseLidState shouldBe 0x11.toUByte() - rawDeviceColor shouldBe 0x0.toUByte() - rawSuffix shouldBe 0x04.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x2420.toUShort() + pubStatus shouldBe 0x0B.toUByte() + pubPodsBattery shouldBe 0x99.toUByte() + pubFlags shouldBe 0x8.toUShort() + pubCaseBattery shouldBe 0xF.toUShort() + pubCaseLidState shouldBe 0x11.toUByte() + pubDeviceColor shouldBe 0x0.toUByte() + pubSuffix shouldBe 0x04.toUByte() isLeftPodMicrophone shouldBe false isRightPodMicrophone shouldBe true diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsProTest.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsProTest.kt index c131a090..e66d47f4 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsProTest.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/airpods/AirPodsProTest.kt @@ -13,15 +13,15 @@ class AirPodsProTest : BaseAirPodsTest() { fun `test AirPods Pro - default changed and in case`() = runTest { create("07 19 01 0E 20 54 AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x0e20.toUShort() - rawStatus shouldBe 0x54.toUByte() - rawPodsBattery shouldBe 0xAA.toUByte() - rawFlags shouldBe 0xB.toUShort() - rawCaseBattery shouldBe 0x5.toUShort() - rawCaseLidState shouldBe 0x31.toUByte() - rawDeviceColor shouldBe 0x00.toUByte() - rawSuffix shouldBe 0x00.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x0e20.toUShort() + pubStatus shouldBe 0x54.toUByte() + pubPodsBattery shouldBe 0xAA.toUByte() + pubFlags shouldBe 0xB.toUShort() + pubCaseBattery shouldBe 0x5.toUShort() + pubCaseLidState shouldBe 0x31.toUByte() + pubDeviceColor shouldBe 0x00.toUByte() + pubSuffix shouldBe 0x00.toUByte() isLeftPodMicrophone shouldBe true isRightPodMicrophone shouldBe false @@ -177,7 +177,7 @@ class AirPodsProTest : BaseAirPodsTest() { @Test fun `left pod has no data`() = runTest { create("07 19 01 0E 20 0B F9 8F 03 00 05 5B 59 67 4C F7 F3 EF 01 BA F4 92 1B 26 E4 90 40") { - rawPodsBattery shouldBe 0xF9.toUByte() + pubPodsBattery shouldBe 0xF9.toUByte() batteryLeftPodPercent shouldBe null batteryRightPodPercent shouldBe 0.9f diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/BeatsFitProTest.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/BeatsFitProTest.kt index a01d9a49..e68ef264 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/BeatsFitProTest.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/BeatsFitProTest.kt @@ -15,15 +15,15 @@ class BeatsFitProTest : BaseAirPodsTest() { @Test fun `test basics`() = runTest { create("07 19 01 12 20 20 FA 8F 01 11 24 9B 9B 23 52 60 5A 8C 32 1C A5 C2 81 51 82 AF C8") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x1220.toUShort() - rawStatus shouldBe 0x20.toUByte() - rawPodsBattery shouldBe 0xFA.toUByte() - rawFlags shouldBe 0x8.toUShort() - rawCaseBattery shouldBe 0xF.toUShort() - rawCaseLidState shouldBe 0x01.toUByte() - rawDeviceColor shouldBe 0x11.toUByte() - rawSuffix shouldBe 0x24.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x1220.toUShort() + pubStatus shouldBe 0x20.toUByte() + pubPodsBattery shouldBe 0xFA.toUByte() + pubFlags shouldBe 0x8.toUShort() + pubCaseBattery shouldBe 0xF.toUShort() + pubCaseLidState shouldBe 0x01.toUByte() + pubDeviceColor shouldBe 0x11.toUByte() + pubSuffix shouldBe 0x24.toUByte() isLeftPodMicrophone shouldBe true isRightPodMicrophone shouldBe false @@ -44,15 +44,15 @@ class BeatsFitProTest : BaseAirPodsTest() { @Test fun `extra rl test case`() = runTest { create("07 19 01 12 20 04 FA 92 54 11 24 CE B1 DF 9D 8D F5 E3 37 60 B1 23 8B 90 3B 63 3F") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x1220.toUShort() - rawStatus shouldBe 0x04.toUByte() - rawPodsBattery shouldBe 0xFA.toUByte() - rawFlags shouldBe 0x9.toUShort() - rawCaseBattery shouldBe 0x2.toUShort() - rawCaseLidState shouldBe 0x54.toUByte() - rawDeviceColor shouldBe 0x11.toUByte() - rawSuffix shouldBe 0x24.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x1220.toUShort() + pubStatus shouldBe 0x04.toUByte() + pubPodsBattery shouldBe 0xFA.toUByte() + pubFlags shouldBe 0x9.toUShort() + pubCaseBattery shouldBe 0x2.toUShort() + pubCaseLidState shouldBe 0x54.toUByte() + pubDeviceColor shouldBe 0x11.toUByte() + pubSuffix shouldBe 0x24.toUByte() isLeftPodMicrophone shouldBe false isRightPodMicrophone shouldBe true diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/BeatsFlexText.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/BeatsFlexText.kt index 0f591c34..6c0340f1 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/BeatsFlexText.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/BeatsFlexText.kt @@ -12,15 +12,15 @@ class BeatsFlexText : BaseAirPodsTest() { @Test fun `default BeatsFlex`() = runTest { create("07 19 01 10 20 0A F4 8F 00 01 00 C4 71 9F 9C EF A2 E3 BA 66 FE 1D 45 9F C9 2F A0") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x1020.toUShort() - rawStatus shouldBe 0x0A.toUByte() - rawPodsBattery shouldBe 0xF4.toUByte() - rawFlags shouldBe 0x8.toUShort() - rawCaseBattery shouldBe 0xF.toUShort() - rawCaseLidState shouldBe 0x00.toUByte() - rawDeviceColor shouldBe 0x01.toUByte() - rawSuffix shouldBe 0x00.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x1020.toUShort() + pubStatus shouldBe 0x0A.toUByte() + pubPodsBattery shouldBe 0xF4.toUByte() + pubFlags shouldBe 0x8.toUShort() + pubCaseBattery shouldBe 0xF.toUShort() + pubCaseLidState shouldBe 0x00.toUByte() + pubDeviceColor shouldBe 0x01.toUByte() + pubSuffix shouldBe 0x00.toUByte() batteryHeadsetPercent shouldBe 0.4f diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/BeatsSolo3Test.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/BeatsSolo3Test.kt index 1287e0ec..ce0832b9 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/BeatsSolo3Test.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/BeatsSolo3Test.kt @@ -12,15 +12,15 @@ class BeatsSolo3Test : BaseAirPodsTest() { @Test fun `default BeatsSolo3`() = runTest { create("07 19 01 06 20 62 04 80 01 0F 40 0D 70 50 16 F2 40 83 16 BF 10 16 34 9B 74 84 E8") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x0620.toUShort() - rawStatus shouldBe 0x62.toUByte() - rawPodsBattery shouldBe 0x04.toUByte() - rawFlags shouldBe 0x8.toUShort() - rawCaseBattery shouldBe 0x0.toUShort() - rawCaseLidState shouldBe 0x01.toUByte() - rawDeviceColor shouldBe 0x0F.toUByte() - rawSuffix shouldBe 0x40.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x0620.toUShort() + pubStatus shouldBe 0x62.toUByte() + pubPodsBattery shouldBe 0x04.toUByte() + pubFlags shouldBe 0x8.toUShort() + pubCaseBattery shouldBe 0x0.toUShort() + pubCaseLidState shouldBe 0x01.toUByte() + pubDeviceColor shouldBe 0x0F.toUByte() + pubSuffix shouldBe 0x40.toUByte() batteryHeadsetPercent shouldBe 0.4f diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/BeatsStudio3Test.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/BeatsStudio3Test.kt index 1e0c2b11..fbfe3a78 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/BeatsStudio3Test.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/BeatsStudio3Test.kt @@ -12,15 +12,15 @@ class BeatsStudio3Test : BaseAirPodsTest() { @Test fun `default BeatsStudio3`() = runTest { create("07 19 01 09 20 62 04 80 01 0F 40 0D 70 50 16 F2 40 83 16 BF 10 16 34 9B 74 84 E8") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x0920.toUShort() - rawStatus shouldBe 0x62.toUByte() - rawPodsBattery shouldBe 0x04.toUByte() - rawFlags shouldBe 0x8.toUShort() - rawCaseBattery shouldBe 0x0.toUShort() - rawCaseLidState shouldBe 0x01.toUByte() - rawDeviceColor shouldBe 0x0F.toUByte() - rawSuffix shouldBe 0x40.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x0920.toUShort() + pubStatus shouldBe 0x62.toUByte() + pubPodsBattery shouldBe 0x04.toUByte() + pubFlags shouldBe 0x8.toUShort() + pubCaseBattery shouldBe 0x0.toUShort() + pubCaseLidState shouldBe 0x01.toUByte() + pubDeviceColor shouldBe 0x0F.toUByte() + pubSuffix shouldBe 0x40.toUByte() batteryHeadsetPercent shouldBe 0.4f diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/BeatsXTest.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/BeatsXTest.kt index a645d751..773862da 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/BeatsXTest.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/BeatsXTest.kt @@ -12,15 +12,15 @@ class BeatsXTest : BaseAirPodsTest() { @Test fun `default BeatsX`() = runTest { create("07 19 01 05 20 00 F5 0F 01 01 00 6D CE C0 04 22 0A 85 31 2D 82 6B 42 80 01 20 1A") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x0520.toUShort() - rawStatus shouldBe 0x00.toUByte() - rawPodsBattery shouldBe 0xF5.toUByte() - rawFlags shouldBe 0x0.toUShort() - rawCaseBattery shouldBe 0xF.toUShort() - rawCaseLidState shouldBe 0x01.toUByte() - rawDeviceColor shouldBe 0x01.toUByte() - rawSuffix shouldBe 0x00.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x0520.toUShort() + pubStatus shouldBe 0x00.toUByte() + pubPodsBattery shouldBe 0xF5.toUByte() + pubFlags shouldBe 0x0.toUShort() + pubCaseBattery shouldBe 0xF.toUShort() + pubCaseLidState shouldBe 0x01.toUByte() + pubDeviceColor shouldBe 0x01.toUByte() + pubSuffix shouldBe 0x00.toUByte() batteryHeadsetPercent shouldBe 0.5f diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/PowerBeats3Test.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/PowerBeats3Test.kt index ea4c7928..83cbe7e2 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/PowerBeats3Test.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/PowerBeats3Test.kt @@ -12,15 +12,15 @@ class PowerBeats3Test : BaseAirPodsTest() { @Test fun `default PowerBeats3`() = runTest { create("07 19 01 03 20 62 04 80 01 0F 40 0D 70 50 16 F2 40 83 16 BF 10 16 34 9B 74 84 E8") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x0320.toUShort() - rawStatus shouldBe 0x62.toUByte() - rawPodsBattery shouldBe 0x04.toUByte() - rawFlags shouldBe 0x8.toUShort() - rawCaseBattery shouldBe 0x0.toUShort() - rawCaseLidState shouldBe 0x01.toUByte() - rawDeviceColor shouldBe 0x0F.toUByte() - rawSuffix shouldBe 0x40.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x0320.toUShort() + pubStatus shouldBe 0x62.toUByte() + pubPodsBattery shouldBe 0x04.toUByte() + pubFlags shouldBe 0x8.toUShort() + pubCaseBattery shouldBe 0x0.toUShort() + pubCaseLidState shouldBe 0x01.toUByte() + pubDeviceColor shouldBe 0x0F.toUByte() + pubSuffix shouldBe 0x40.toUByte() batteryHeadsetPercent shouldBe 0.4f diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/PowerBeats4Test.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/PowerBeats4Test.kt index e44f69bc..4fa13634 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/PowerBeats4Test.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/PowerBeats4Test.kt @@ -12,15 +12,15 @@ class PowerBeats4Test : BaseAirPodsTest() { @Test fun `playing music`() = runTest { create("07 19 01 0D 20 00 02 80 01 00 05 07 C5 E1 9C BD 9A 05 0E AE 9E 56 53 2F F9 75 4A") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x0D20.toUShort() - rawStatus shouldBe 0x0.toUByte() - rawPodsBattery shouldBe 0x02.toUByte() - rawFlags shouldBe 0x8.toUShort() - rawCaseBattery shouldBe 0x0.toUShort() - rawCaseLidState shouldBe 0x01.toUByte() - rawDeviceColor shouldBe 0x00.toUByte() - rawSuffix shouldBe 0x05.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x0D20.toUShort() + pubStatus shouldBe 0x0.toUByte() + pubPodsBattery shouldBe 0x02.toUByte() + pubFlags shouldBe 0x8.toUShort() + pubCaseBattery shouldBe 0x0.toUShort() + pubCaseLidState shouldBe 0x01.toUByte() + pubDeviceColor shouldBe 0x00.toUByte() + pubSuffix shouldBe 0x05.toUByte() batteryHeadsetPercent shouldBe 0.2f @@ -33,15 +33,15 @@ class PowerBeats4Test : BaseAirPodsTest() { @Test fun `extra test case`() = runTest { create("07 19 01 0D 20 00 02 80 02 00 04 80 90 60 77 32 C2 0C 75 7A 3D 7E D7 1C 0E B8 43") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x0D20.toUShort() - rawStatus shouldBe 0x0.toUByte() - rawPodsBattery shouldBe 0x02.toUByte() - rawFlags shouldBe 0x8.toUShort() - rawCaseBattery shouldBe 0x0.toUShort() - rawCaseLidState shouldBe 0x02.toUByte() - rawDeviceColor shouldBe 0x00.toUByte() - rawSuffix shouldBe 0x04.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x0D20.toUShort() + pubStatus shouldBe 0x0.toUByte() + pubPodsBattery shouldBe 0x02.toUByte() + pubFlags shouldBe 0x8.toUShort() + pubCaseBattery shouldBe 0x0.toUShort() + pubCaseLidState shouldBe 0x02.toUByte() + pubDeviceColor shouldBe 0x00.toUByte() + pubSuffix shouldBe 0x04.toUByte() batteryHeadsetPercent shouldBe 0.2f @@ -55,15 +55,15 @@ class PowerBeats4Test : BaseAirPodsTest() { @Test fun `disconnected state`() = runTest { create("07 19 01 0D 20 00 02 80 01 00 00 25 DF 66 40 AF 44 7B 77 95 8F D1 92 50 26 11 74") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x0D20.toUShort() - rawStatus shouldBe 0x0.toUByte() - rawPodsBattery shouldBe 0x02.toUByte() - rawFlags shouldBe 0x8.toUShort() - rawCaseBattery shouldBe 0x0.toUShort() - rawCaseLidState shouldBe 0x01.toUByte() - rawDeviceColor shouldBe 0x00.toUByte() - rawSuffix shouldBe 0x00.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x0D20.toUShort() + pubStatus shouldBe 0x0.toUByte() + pubPodsBattery shouldBe 0x02.toUByte() + pubFlags shouldBe 0x8.toUShort() + pubCaseBattery shouldBe 0x0.toUShort() + pubCaseLidState shouldBe 0x01.toUByte() + pubDeviceColor shouldBe 0x00.toUByte() + pubSuffix shouldBe 0x00.toUByte() batteryHeadsetPercent shouldBe 0.2f diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsProTest.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsProTest.kt index bad843f0..ac77e365 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsProTest.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsProTest.kt @@ -13,15 +13,15 @@ class PowerBeatsProTest : BaseAirPodsTest() { @Test fun `test PowerBeatsPro`() = runTest { create("07 19 01 0B 20 54 AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x0B20.toUShort() - rawStatus shouldBe 0x54.toUByte() - rawPodsBattery shouldBe 0xAA.toUByte() - rawFlags shouldBe 0xB.toUShort() - rawCaseBattery shouldBe 0x5.toUShort() - rawCaseLidState shouldBe 0x31.toUByte() - rawDeviceColor shouldBe 0x00.toUByte() - rawSuffix shouldBe 0x00.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x0B20.toUShort() + pubStatus shouldBe 0x54.toUByte() + pubPodsBattery shouldBe 0xAA.toUByte() + pubFlags shouldBe 0xB.toUShort() + pubCaseBattery shouldBe 0x5.toUShort() + pubCaseLidState shouldBe 0x31.toUByte() + pubDeviceColor shouldBe 0x00.toUByte() + pubSuffix shouldBe 0x00.toUByte() isLeftPodMicrophone shouldBe true isRightPodMicrophone shouldBe false diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen1Test.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen1Test.kt index 4a3c9631..8b093b62 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen1Test.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen1Test.kt @@ -11,15 +11,15 @@ class FakeAirPodsGen1Test : BaseAirPodsTest() { @Test fun `charging in box`() = runTest { create("07 13 01 0F 20 71 AA 37 32 00 10 00 64 64 FF 00 00 00 00 00 00") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x0F20.toUShort() - rawStatus shouldBe 0x71.toUByte() - rawPodsBattery shouldBe 0xAA.toUByte() - rawFlags shouldBe 0x3.toUShort() - rawCaseBattery shouldBe 0x7.toUShort() - rawCaseLidState shouldBe 0x32.toUByte() - rawDeviceColor shouldBe 0x00.toUByte() - rawSuffix shouldBe 0x10.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x0F20.toUShort() + pubStatus shouldBe 0x71.toUByte() + pubPodsBattery shouldBe 0xAA.toUByte() + pubFlags shouldBe 0x3.toUShort() + pubCaseBattery shouldBe 0x7.toUShort() + pubCaseLidState shouldBe 0x32.toUByte() + pubDeviceColor shouldBe 0x00.toUByte() + pubSuffix shouldBe 0x10.toUByte() batteryLeftPodPercent shouldBe 1.0f batteryRightPodPercent shouldBe 1.0f diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen2Test.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen2Test.kt index d34b9f5e..d4d1dee7 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen2Test.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen2Test.kt @@ -11,15 +11,15 @@ class FakeAirPodsGen2Test : BaseAirPodsTest() { @Test fun `charging in box`() = runTest { create("07 13 01 02 20 71 AA 37 32 00 10 00 64 64 FF 00 00 00 00 00 00") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x0220.toUShort() - rawStatus shouldBe 0x71.toUByte() - rawPodsBattery shouldBe 0xAA.toUByte() - rawFlags shouldBe 0x3.toUShort() - rawCaseBattery shouldBe 0x7.toUShort() - rawCaseLidState shouldBe 0x32.toUByte() - rawDeviceColor shouldBe 0x00.toUByte() - rawSuffix shouldBe 0x10.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x0220.toUShort() + pubStatus shouldBe 0x71.toUByte() + pubPodsBattery shouldBe 0xAA.toUByte() + pubFlags shouldBe 0x3.toUShort() + pubCaseBattery shouldBe 0x7.toUShort() + pubCaseLidState shouldBe 0x32.toUByte() + pubDeviceColor shouldBe 0x00.toUByte() + pubSuffix shouldBe 0x10.toUByte() batteryLeftPodPercent shouldBe 1.0f batteryRightPodPercent shouldBe 1.0f diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen3Test.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen3Test.kt index 029c023a..d238a6e0 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen3Test.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen3Test.kt @@ -11,15 +11,15 @@ class FakeAirPodsGen3Test : BaseAirPodsTest() { @Test fun `charging in case`() = runTest { create("07 13 01 13 20 75 AA 37 34 00 10 00 E4 E4 64 00 00 00 00 00 00") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x1320.toUShort() - rawStatus shouldBe 0x75.toUByte() - rawPodsBattery shouldBe 0xAA.toUByte() - rawFlags shouldBe 0x3.toUShort() - rawCaseBattery shouldBe 0x7.toUShort() - rawCaseLidState shouldBe 0x34.toUByte() - rawDeviceColor shouldBe 0x00.toUByte() - rawSuffix shouldBe 0x10.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x1320.toUShort() + pubStatus shouldBe 0x75.toUByte() + pubPodsBattery shouldBe 0xAA.toUByte() + pubFlags shouldBe 0x3.toUShort() + pubCaseBattery shouldBe 0x7.toUShort() + pubCaseLidState shouldBe 0x34.toUByte() + pubDeviceColor shouldBe 0x00.toUByte() + pubSuffix shouldBe 0x10.toUByte() batteryLeftPodPercent shouldBe 1f batteryRightPodPercent shouldBe 1f diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro2Test.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro2Test.kt index 57e77069..81848e92 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro2Test.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro2Test.kt @@ -11,15 +11,15 @@ class FakeAirPodsPro2Test : BaseAirPodsTest() { @Test fun `guessed data`() = runTest { create("07 13 01 14 20 75 AA 58 35 00 10 00 E4 E4 26 00 00 00 00 00 00") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x1420.toUShort() - rawStatus shouldBe 0x75.toUByte() - rawPodsBattery shouldBe 0xAA.toUByte() - rawFlags shouldBe 0x5.toUShort() - rawCaseBattery shouldBe 0x8.toUShort() - rawCaseLidState shouldBe 0x35.toUByte() - rawDeviceColor shouldBe 0x00.toUByte() - rawSuffix shouldBe 0x10.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x1420.toUShort() + pubStatus shouldBe 0x75.toUByte() + pubPodsBattery shouldBe 0xAA.toUByte() + pubFlags shouldBe 0x5.toUShort() + pubCaseBattery shouldBe 0x8.toUShort() + pubCaseLidState shouldBe 0x35.toUByte() + pubDeviceColor shouldBe 0x00.toUByte() + pubSuffix shouldBe 0x10.toUByte() batteryLeftPodPercent shouldBe 1.0f batteryRightPodPercent shouldBe 1.0f @@ -38,15 +38,15 @@ class FakeAirPodsPro2Test : BaseAirPodsTest() { @Test fun `user supplied`() = runTest { create("07 13 01 14 20 75 AA 72 39 00 00 6F E4 E4 93 30 00 30 30 30 30") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x1420.toUShort() - rawStatus shouldBe 0x75.toUByte() - rawPodsBattery shouldBe 0xAA.toUByte() - rawFlags shouldBe 0x7.toUShort() - rawCaseBattery shouldBe 0x2.toUShort() - rawCaseLidState shouldBe 0x39.toUByte() - rawDeviceColor shouldBe 0x00.toUByte() - rawSuffix shouldBe 0x0.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x1420.toUShort() + pubStatus shouldBe 0x75.toUByte() + pubPodsBattery shouldBe 0xAA.toUByte() + pubFlags shouldBe 0x7.toUShort() + pubCaseBattery shouldBe 0x2.toUShort() + pubCaseLidState shouldBe 0x39.toUByte() + pubDeviceColor shouldBe 0x00.toUByte() + pubSuffix shouldBe 0x0.toUByte() batteryLeftPodPercent shouldBe 1.0f batteryRightPodPercent shouldBe 1.0f diff --git a/app-common/src/test/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsProTest.kt b/app-common/src/test/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsProTest.kt index 3fe05a9b..3466a269 100644 --- a/app-common/src/test/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsProTest.kt +++ b/app-common/src/test/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsProTest.kt @@ -11,15 +11,15 @@ class FakeAirPodsProTest : BaseAirPodsTest() { @Test fun `guessed data`() = runTest { create("07 13 01 0E 20 71 AA 37 36 00 10 00 FF 64 FF 00 00 00 00 00 00") { - rawPrefix shouldBe 0x01.toUByte() - rawDeviceModel shouldBe 0x0E20.toUShort() - rawStatus shouldBe 0x71.toUByte() - rawPodsBattery shouldBe 0xAA.toUByte() - rawFlags shouldBe 0x3.toUShort() - rawCaseBattery shouldBe 0x7.toUShort() - rawCaseLidState shouldBe 0x36.toUByte() - rawDeviceColor shouldBe 0x00.toUByte() - rawSuffix shouldBe 0x10.toUByte() + pubPrefix shouldBe 0x01.toUByte() + pubDeviceModel shouldBe 0x0E20.toUShort() + pubStatus shouldBe 0x71.toUByte() + pubPodsBattery shouldBe 0xAA.toUByte() + pubFlags shouldBe 0x3.toUShort() + pubCaseBattery shouldBe 0x7.toUShort() + pubCaseLidState shouldBe 0x36.toUByte() + pubDeviceColor shouldBe 0x00.toUByte() + pubSuffix shouldBe 0x10.toUByte() batteryLeftPodPercent shouldBe 1.0f batteryRightPodPercent shouldBe 1.0f