From ce25ad671125a3ccb4109fd1432c1a6da261d6a1 Mon Sep 17 00:00:00 2001 From: darken Date: Sat, 12 Feb 2022 07:45:47 +0100 Subject: [PATCH] Add support for Tws i99999 --- .../capod/common/bluetooth/FakeBleData.kt | 13 ++++ .../eu/darken/capod/pods/core/PodDevice.kt | 3 + .../capod/pods/core/apple/AppleFactory.kt | 2 +- .../pods/core/apple/AppleFactoryModule.kt | 3 + .../capod/pods/core/apple/ApplePodsFactory.kt | 6 +- .../pods/core/apple/UnknownAppleDevice.kt | 6 +- .../pods/core/apple/airpods/AirPodsGen1.kt | 10 +-- .../pods/core/apple/airpods/AirPodsGen2.kt | 9 +-- .../pods/core/apple/airpods/AirPodsGen3.kt | 9 +-- .../pods/core/apple/airpods/AirPodsMax.kt | 9 +-- .../pods/core/apple/airpods/AirPodsPro.kt | 9 +-- .../capod/pods/core/apple/beats/BeatsFlex.kt | 9 +-- .../capod/pods/core/apple/beats/BeatsSolo3.kt | 9 +-- .../pods/core/apple/beats/BeatsStudio3.kt | 9 +-- .../capod/pods/core/apple/beats/BeatsX.kt | 9 +-- .../pods/core/apple/beats/PowerBeats3.kt | 9 +-- .../pods/core/apple/beats/PowerBeatsPro.kt | 9 +-- .../capod/pods/core/apple/fakes/Twsi99999.kt | 72 +++++++++++++++++++ .../core/apple/protocol/ProximityPairing.kt | 10 ++- .../pods/core/apple/fakes/Twsi99999Test.kt | 42 +++++++++++ 20 files changed, 200 insertions(+), 57 deletions(-) create mode 100644 app/src/main/java/eu/darken/capod/pods/core/apple/fakes/Twsi99999.kt create mode 100644 app/src/test/java/eu/darken/capod/pods/core/apple/fakes/Twsi99999Test.kt diff --git a/app/src/main/java/eu/darken/capod/common/bluetooth/FakeBleData.kt b/app/src/main/java/eu/darken/capod/common/bluetooth/FakeBleData.kt index 54489e8c..06ca5121 100644 --- a/app/src/main/java/eu/darken/capod/common/bluetooth/FakeBleData.kt +++ b/app/src/main/java/eu/darken/capod/common/bluetooth/FakeBleData.kt @@ -75,6 +75,19 @@ class FakeBleData @Inject constructor( fakeDevices.add(this) } } + + // Tws i99999 + BleScanResult( + address = "5E:9E:D1:29:D2:6D", + rssi = Random.nextInt(15, 75) * -1, + generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 400, + manufacturerSpecificData = mapOf(76 to "07 13 01 02 20 71 AA 37 32 00 10 00 64 64 FF 00 00 00 00 00 00".hexToByteArray()) + ).run { + if (Random.nextBoolean()) { + fakeDevices.add(this) + } + } + // Unknown Device BleScanResult( address = "6E:9E:D1:49:D2:6D", diff --git a/app/src/main/java/eu/darken/capod/pods/core/PodDevice.kt b/app/src/main/java/eu/darken/capod/pods/core/PodDevice.kt index 710ae7f4..b3b55a5b 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/PodDevice.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/PodDevice.kt @@ -100,6 +100,9 @@ interface PodDevice { @Json(name = "beats.powerbeats.pro") POWERBEATS_PRO( "Power Beats Pro" ), + @Json(name = "fakes.tws.i99999") TWS_I99999( + "TWS i99999" + ), @Json(name = "unknown") UNKNOWN( "Unknown" ); diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/AppleFactory.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/AppleFactory.kt index 926222dd..0128fd99 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/AppleFactory.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/AppleFactory.kt @@ -56,7 +56,7 @@ class AppleFactory @Inject constructor( val device = (factory ?: unknownAppleFactory).create( scanResult = scanResult, - proximityMessage = pm, + message = pm, ) if (factory == null && !SILENCED_PMS.contains(device.rawDeviceModel) && scanResult.address != "6E:9E:D1:49:D2:6D") { diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/AppleFactoryModule.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/AppleFactoryModule.kt index 9002e95a..363c6f62 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/AppleFactoryModule.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/AppleFactoryModule.kt @@ -7,6 +7,7 @@ import dagger.hilt.components.SingletonComponent import dagger.multibindings.IntoSet import eu.darken.capod.pods.core.apple.airpods.* import eu.darken.capod.pods.core.apple.beats.* +import eu.darken.capod.pods.core.apple.fakes.Twsi99999 @InstallIn(SingletonComponent::class) @Module @@ -25,4 +26,6 @@ abstract class AppleFactoryModule { @Binds @IntoSet abstract fun beatsX(factory: BeatsX.Factory): ApplePodsFactory @Binds @IntoSet abstract fun powerBeats3(factory: PowerBeats3.Factory): ApplePodsFactory @Binds @IntoSet abstract fun powerBeatsPro(factory: PowerBeatsPro.Factory): ApplePodsFactory + + @Binds @IntoSet abstract fun fakesTwsi999999(factory: Twsi99999.Factory): ApplePodsFactory } \ No newline at end of file diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ApplePodsFactory.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ApplePodsFactory.kt index 3b663123..1756f10c 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ApplePodsFactory.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ApplePodsFactory.kt @@ -23,7 +23,7 @@ abstract class ApplePodsFactory(private val tag: String) { fun ProximityPairing.Message.getApplePodsMarkings(): Markings = Markings( vendor = ProximityPairing.CONTINUITY_PROTOCOL_MESSAGE_TYPE_PROXIMITY_PAIRING, - length = ProximityPairing.PROXIMITY_PAIRING_MESSAGE_LENGTH.toUByte(), + length = ProximityPairing.PAIRING_MESSAGE_LENGTH.toUByte(), device = (((data[1].toInt() and 255) shl 8) or (data[2].toInt() and 255)).toUShort(), // Make comparison order independent podBatteryData = setOf(data[4].upperNibble, data[4].lowerNibble), @@ -138,10 +138,10 @@ abstract class ApplePodsFactory(private val tag: String) { dirty = data[1] ) - abstract fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean + abstract fun isResponsible(message: ProximityPairing.Message): Boolean abstract fun create( scanResult: BleScanResult, - proximityMessage: ProximityPairing.Message, + message: ProximityPairing.Message, ): ApplePods } \ No newline at end of file diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/UnknownAppleDevice.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/UnknownAppleDevice.kt index c6630f55..860c29d4 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/UnknownAppleDevice.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/UnknownAppleDevice.kt @@ -28,13 +28,13 @@ data class UnknownAppleDevice( get() = rssiAverage ?: super.rssi class Factory @Inject constructor() : ApplePodsFactory(TAG) { - override fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean = true + override fun isResponsible(message: ProximityPairing.Message): Boolean = true override fun create( scanResult: BleScanResult, - proximityMessage: ProximityPairing.Message, + message: ProximityPairing.Message, ): ApplePods { - var basic = UnknownAppleDevice(scanResult = scanResult, proximityMessage = proximityMessage) + var basic = UnknownAppleDevice(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen1.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen1.kt index b4935b85..ca9d8341 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen1.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen1.kt @@ -36,11 +36,13 @@ data class AirPodsGen1 constructor( class Factory @Inject constructor() : DualApplePodsFactory(TAG) { - override fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean = - proximityMessage.getModelInfo().full == DEVICE_CODE + override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH + } - override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods { - var basic = AirPodsGen1(scanResult = scanResult, proximityMessage = proximityMessage) + + override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + var basic = AirPodsGen1(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen2.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen2.kt index a6516255..cf3a8d47 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen2.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen2.kt @@ -36,11 +36,12 @@ data class AirPodsGen2 constructor( class Factory @Inject constructor() : DualApplePodsFactory(TAG) { - override fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean = - proximityMessage.getModelInfo().full == DEVICE_CODE + override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH + } - override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods { - var basic = AirPodsGen2(scanResult = scanResult, proximityMessage = proximityMessage) + override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + var basic = AirPodsGen2(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen3.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen3.kt index 9874fdc3..b3bc919d 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen3.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen3.kt @@ -35,11 +35,12 @@ data class AirPodsGen3 constructor( class Factory @Inject constructor() : DualApplePodsFactory(TAG) { - override fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean = - proximityMessage.getModelInfo().full == DEVICE_CODE + override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH + } - override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods { - var basic = AirPodsGen3(scanResult = scanResult, proximityMessage = proximityMessage) + override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + var basic = AirPodsGen3(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMax.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMax.kt index b0f79ed8..dac4505e 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMax.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMax.kt @@ -28,11 +28,12 @@ data class AirPodsMax( class Factory @Inject constructor() : SingleApplePodsFactory(TAG) { - override fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean = - proximityMessage.getModelInfo().dirty == DEVICE_CODE_DIRTY + override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + getModelInfo().dirty == DEVICE_CODE_DIRTY && length == ProximityPairing.PAIRING_MESSAGE_LENGTH + } - override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods { - var basic = AirPodsMax(scanResult = scanResult, proximityMessage = proximityMessage) + override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + var basic = AirPodsMax(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro.kt index 08d2166d..25d03799 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro.kt @@ -37,11 +37,12 @@ data class AirPodsPro( class Factory @Inject constructor() : DualApplePodsFactory(TAG) { - override fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean = - proximityMessage.getModelInfo().full == DEVICE_CODE + override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH + } - override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods { - var basic = AirPodsPro(scanResult = scanResult, proximityMessage = proximityMessage) + override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + var basic = AirPodsPro(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFlex.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFlex.kt index 87b5ac81..9b783b9a 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFlex.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFlex.kt @@ -28,11 +28,12 @@ data class BeatsFlex( class Factory @Inject constructor() : BasicSingleApplePodsFactory(TAG) { - override fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean = - proximityMessage.getModelInfo().full == DEVICE_CODE + override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH + } - override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods { - var basic = BeatsFlex(scanResult = scanResult, proximityMessage = proximityMessage) + override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + var basic = BeatsFlex(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsSolo3.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsSolo3.kt index 85ce39d3..7f198481 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsSolo3.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsSolo3.kt @@ -27,11 +27,12 @@ data class BeatsSolo3( class Factory @Inject constructor() : BasicSingleApplePodsFactory(TAG) { - override fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean = - proximityMessage.getModelInfo().full == DEVICE_CODE + override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH + } - override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods { - var basic = BeatsSolo3(scanResult = scanResult, proximityMessage = proximityMessage) + override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + var basic = BeatsSolo3(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsStudio3.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsStudio3.kt index 2cba33ba..4f556488 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsStudio3.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsStudio3.kt @@ -25,11 +25,12 @@ data class BeatsStudio3( class Factory @Inject constructor() : BasicSingleApplePodsFactory(TAG) { - override fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean = - proximityMessage.getModelInfo().dirty == DEVICE_CODE_DIRTY + override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + getModelInfo().dirty == DEVICE_CODE_DIRTY && length == ProximityPairing.PAIRING_MESSAGE_LENGTH + } - override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods { - var basic = BeatsStudio3(scanResult = scanResult, proximityMessage = proximityMessage) + override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + var basic = BeatsStudio3(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsX.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsX.kt index 251214ed..0df41bb8 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsX.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsX.kt @@ -28,11 +28,12 @@ data class BeatsX( class Factory @Inject constructor() : BasicSingleApplePodsFactory(TAG) { - override fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean = - proximityMessage.getModelInfo().full == DEVICE_CODE + override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH + } - override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods { - var basic = BeatsX(scanResult = scanResult, proximityMessage = proximityMessage) + override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + var basic = BeatsX(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats3.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats3.kt index 2f6b065a..1547dfdc 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats3.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats3.kt @@ -27,11 +27,12 @@ data class PowerBeats3( class Factory @Inject constructor() : BasicSingleApplePodsFactory(TAG) { - override fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean = - proximityMessage.getModelInfo().full == DEVICE_CODE + override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH + } - override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods { - var basic = PowerBeats3(scanResult = scanResult, proximityMessage = proximityMessage) + override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + var basic = PowerBeats3(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro.kt index 32c67e04..960b1014 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro.kt @@ -36,11 +36,12 @@ data class PowerBeatsPro( class Factory @Inject constructor() : DualApplePodsFactory(TAG) { - override fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean = - proximityMessage.getModelInfo().dirty == DEVICE_CODE_DIRTY + override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run { + getModelInfo().dirty == DEVICE_CODE_DIRTY && length == ProximityPairing.PAIRING_MESSAGE_LENGTH + } - override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods { - var basic = PowerBeatsPro(scanResult = scanResult, proximityMessage = proximityMessage) + override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + var basic = PowerBeatsPro(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/fakes/Twsi99999.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/fakes/Twsi99999.kt new file mode 100644 index 00000000..ef5c8b94 --- /dev/null +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/fakes/Twsi99999.kt @@ -0,0 +1,72 @@ +package eu.darken.capod.pods.core.apple.fakes + +import eu.darken.capod.common.bluetooth.BleScanResult +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.DualApplePods +import eu.darken.capod.pods.core.apple.DualApplePodsFactory +import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import java.time.Instant +import javax.inject.Inject + +/** + * Basically an AirPods GEN1 clone + */ +data class Twsi99999 constructor( + 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 confidence: Float = PodDevice.BASE_CONFIDENCE, + private val rssiAverage: Int? = null, + private val cachedBatteryPercentage: Float? = null, + private val cachedCaseState: DualApplePods.LidState? = null +) : DualApplePods { + + override val model: PodDevice.Model = PodDevice.Model.TWS_I99999 + + override val batteryCasePercent: Float? + get() = super.batteryCasePercent ?: cachedBatteryPercentage + + override val caseLidState: DualApplePods.LidState + get() = cachedCaseState ?: super.caseLidState + + override val rssi: Int + get() = rssiAverage ?: super.rssi + + class Factory @Inject constructor() : DualApplePodsFactory(TAG) { + + override fun isResponsible(message: ProximityPairing.Message): 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): ApplePods { + var basic = Twsi99999(scanResult = scanResult, proximityMessage = message) + val result = searchHistory(basic) + + if (result != null) basic = basic.copy(identifier = result.id) + updateHistory(basic) + + if (result == null) return basic + + return basic.copy( + identifier = result.id, + seenFirstAt = result.seenFirstAt, + seenCounter = result.seenCounter, + confidence = result.confidence, + cachedBatteryPercentage = result.getLatestCaseBattery(), + rssiAverage = result.averageRssi(basic.rssi), + cachedCaseState = result.getLatestCaseLidState(basic) + ) + } + } + + companion object { + private val DEVICE_CODE = 0x0220.toUShort() + private val TAG = logTag("PodDevice", "Apple", "TWS", "i99999") + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/protocol/ProximityPairing.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/protocol/ProximityPairing.kt index ea7ee515..bb9ca801 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/protocol/ProximityPairing.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/protocol/ProximityPairing.kt @@ -27,10 +27,6 @@ object ProximityPairing { log { "Not a proximity pairing message: $this" } return null } - if (message.length != PROXIMITY_PAIRING_MESSAGE_LENGTH) { - log { "Proximity pairing message has invalid length." } - return null - } return Message( type = message.type, @@ -43,7 +39,7 @@ object ProximityPairing { fun getBleScanFilter(): Set { val manufacturerData = ByteArray(CONTINUITY_PROTOCOL_MESSAGE_LENGTH).apply { this[0] = CONTINUITY_PROTOCOL_MESSAGE_TYPE_PROXIMITY_PAIRING.toByte() - this[1] = PROXIMITY_PAIRING_MESSAGE_LENGTH.toByte() + this[1] = PAIRING_MESSAGE_LENGTH.toByte() } val manufacturerDataMask = ByteArray(CONTINUITY_PROTOCOL_MESSAGE_LENGTH).apply { @@ -62,5 +58,7 @@ object ProximityPairing { private const val CONTINUITY_PROTOCOL_MESSAGE_LENGTH = 27 internal val CONTINUITY_PROTOCOL_MESSAGE_TYPE_PROXIMITY_PAIRING = 0x07.toUByte() - internal const val PROXIMITY_PAIRING_MESSAGE_LENGTH = 25 + + // This is the default message length among official Apple devices, clones may have different length + internal const val PAIRING_MESSAGE_LENGTH = 25 } diff --git a/app/src/test/java/eu/darken/capod/pods/core/apple/fakes/Twsi99999Test.kt b/app/src/test/java/eu/darken/capod/pods/core/apple/fakes/Twsi99999Test.kt new file mode 100644 index 00000000..d69b4212 --- /dev/null +++ b/app/src/test/java/eu/darken/capod/pods/core/apple/fakes/Twsi99999Test.kt @@ -0,0 +1,42 @@ +package eu.darken.capod.pods.core.apple.fakes + +import eu.darken.capod.pods.core.apple.BaseAirPodsTest +import eu.darken.capod.pods.core.apple.DualApplePods +import io.kotest.matchers.shouldBe +import kotlinx.coroutines.test.runBlockingTest +import org.junit.jupiter.api.Test + +class Twsi99999Test : BaseAirPodsTest() { + + @Test + fun `charging in box`() = runBlockingTest { + 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() + + batteryLeftPodPercent shouldBe 1.0f + batteryRightPodPercent shouldBe 1.0f + + isCaseCharging shouldBe false + isLeftPodCharging shouldBe true + isRightPodCharging shouldBe true + + isLeftPodInEar shouldBe false + isRightPodInEar shouldBe false + batteryCasePercent shouldBe 0.7f + + caseLidState shouldBe DualApplePods.LidState.OPEN + + connectionState shouldBe DualApplePods.ConnectionState.UNKNOWN + + deviceColor shouldBe DualApplePods.DeviceColor.WHITE + } + } +} \ No newline at end of file