From 3b2dcc1a5c6d69aed40f2c5ed394c87449053f3a Mon Sep 17 00:00:00 2001 From: darken Date: Wed, 4 Jun 2025 22:22:44 +0200 Subject: [PATCH] Refactor Pods history to a central `PodHistoryRepo` This moves the history tracking logic for discovered Apple Pods from individual `ApplePodsFactory` implementations to a new `PodHistoryRepo` class. Benefits: - Centralized logic for device history management. - Simplifies individual `ApplePodsFactory` implementations. - Improves code maintainability and testability. --- .../capod/pods/core/apple/AppleFactory.kt | 2 +- .../pods/core/apple/AppleFactoryModule.kt | 46 ++--- .../capod/pods/core/apple/ApplePodsFactory.kt | 190 +----------------- .../pods/core/apple/DualApplePodsFactory.kt | 68 ------- .../pods/core/apple/SingleApplePodsFactory.kt | 3 - .../pods/core/apple/airpods/AirPodsGen1.kt | 11 +- .../pods/core/apple/airpods/AirPodsGen2.kt | 11 +- .../pods/core/apple/airpods/AirPodsGen3.kt | 11 +- .../pods/core/apple/airpods/AirPodsGen4.kt | 11 +- .../pods/core/apple/airpods/AirPodsGen4Anc.kt | 11 +- .../pods/core/apple/airpods/AirPodsMax.kt | 11 +- .../pods/core/apple/airpods/AirPodsMaxUsbc.kt | 11 +- .../pods/core/apple/airpods/AirPodsPro.kt | 11 +- .../pods/core/apple/airpods/AirPodsPro2.kt | 11 +- .../core/apple/airpods/AirPodsPro2Usbc.kt | 11 +- .../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/history/KnownDevice.kt | 68 +++++++ .../pods/core/apple/history/PodHistoryRepo.kt | 178 ++++++++++++++++ .../pods/core/apple/misc/FakeAirPodsGen1.kt | 9 +- .../pods/core/apple/misc/FakeAirPodsGen2.kt | 9 +- .../pods/core/apple/misc/FakeAirPodsGen3.kt | 9 +- .../pods/core/apple/misc/FakeAirPodsPro.kt | 9 +- .../pods/core/apple/misc/FakeAirPodsPro2.kt | 9 +- .../core/apple/misc/UnknownAppleDevice.kt | 9 +- 31 files changed, 442 insertions(+), 365 deletions(-) delete mode 100644 app-common/src/main/java/eu/darken/capod/pods/core/apple/DualApplePodsFactory.kt delete mode 100644 app-common/src/main/java/eu/darken/capod/pods/core/apple/SingleApplePodsFactory.kt create mode 100644 app-common/src/main/java/eu/darken/capod/pods/core/apple/history/KnownDevice.kt create mode 100644 app-common/src/main/java/eu/darken/capod/pods/core/apple/history/PodHistoryRepo.kt 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 f77df345..cac4b2be 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 @@ -23,7 +23,7 @@ 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 podFactories: @JvmSuppressWildcards Set, private val unknownAppleFactory: UnknownAppleDevice.Factory, private val generalSettings: GeneralSettings, private val rpaChecker: RPAChecker, diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/AppleFactoryModule.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/AppleFactoryModule.kt index c0807d06..1cd582b7 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/AppleFactoryModule.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/AppleFactoryModule.kt @@ -33,30 +33,30 @@ import eu.darken.capod.pods.core.apple.misc.FakeAirPodsPro2 @Module abstract class AppleFactoryModule { - @Binds @IntoSet abstract fun airPodsGen1(factory: AirPodsGen1.Factory): ApplePodsFactory - @Binds @IntoSet abstract fun airPodsGen2(factory: AirPodsGen2.Factory): ApplePodsFactory - @Binds @IntoSet abstract fun airPodsGen3(factory: AirPodsGen3.Factory): ApplePodsFactory - @Binds @IntoSet abstract fun airPodsGen4(factory: AirPodsGen4.Factory): ApplePodsFactory - @Binds @IntoSet abstract fun airPodsGen4Anc(factory: AirPodsGen4Anc.Factory): ApplePodsFactory + @Binds @IntoSet abstract fun airPodsGen1(factory: AirPodsGen1.Factory): ApplePodsFactory + @Binds @IntoSet abstract fun airPodsGen2(factory: AirPodsGen2.Factory): ApplePodsFactory + @Binds @IntoSet abstract fun airPodsGen3(factory: AirPodsGen3.Factory): ApplePodsFactory + @Binds @IntoSet abstract fun airPodsGen4(factory: AirPodsGen4.Factory): ApplePodsFactory + @Binds @IntoSet abstract fun airPodsGen4Anc(factory: AirPodsGen4Anc.Factory): ApplePodsFactory - @Binds @IntoSet abstract fun airPodsPro(factory: AirPodsPro.Factory): ApplePodsFactory - @Binds @IntoSet abstract fun airPodsPro2(factory: AirPodsPro2.Factory): ApplePodsFactory - @Binds @IntoSet abstract fun airPodsPro2Usbc(factory: AirPodsPro2Usbc.Factory): ApplePodsFactory - @Binds @IntoSet abstract fun airPodsMax(factory: AirPodsMax.Factory): ApplePodsFactory - @Binds @IntoSet abstract fun airPodsMax2(factory: AirPodsMaxUsbc.Factory): ApplePodsFactory + @Binds @IntoSet abstract fun airPodsPro(factory: AirPodsPro.Factory): ApplePodsFactory + @Binds @IntoSet abstract fun airPodsPro2(factory: AirPodsPro2.Factory): ApplePodsFactory + @Binds @IntoSet abstract fun airPodsPro2Usbc(factory: AirPodsPro2Usbc.Factory): ApplePodsFactory + @Binds @IntoSet abstract fun airPodsMax(factory: AirPodsMax.Factory): ApplePodsFactory + @Binds @IntoSet abstract fun airPodsMax2(factory: AirPodsMaxUsbc.Factory): ApplePodsFactory - @Binds @IntoSet abstract fun beatsFlex(factory: BeatsFlex.Factory): ApplePodsFactory - @Binds @IntoSet abstract fun beatsSolo3(factory: BeatsSolo3.Factory): ApplePodsFactory - @Binds @IntoSet abstract fun beatsStudio3(factory: BeatsStudio3.Factory): ApplePodsFactory - @Binds @IntoSet abstract fun beatsX(factory: BeatsX.Factory): ApplePodsFactory - @Binds @IntoSet abstract fun powerBeats3(factory: PowerBeats3.Factory): ApplePodsFactory - @Binds @IntoSet abstract fun powerBeats4(factory: PowerBeats4.Factory): ApplePodsFactory - @Binds @IntoSet abstract fun powerBeatsPro(factory: PowerBeatsPro.Factory): ApplePodsFactory - @Binds @IntoSet abstract fun beatsFitPro(factory: BeatsFitPro.Factory): ApplePodsFactory + @Binds @IntoSet abstract fun beatsFlex(factory: BeatsFlex.Factory): ApplePodsFactory + @Binds @IntoSet abstract fun beatsSolo3(factory: BeatsSolo3.Factory): ApplePodsFactory + @Binds @IntoSet abstract fun beatsStudio3(factory: BeatsStudio3.Factory): ApplePodsFactory + @Binds @IntoSet abstract fun beatsX(factory: BeatsX.Factory): ApplePodsFactory + @Binds @IntoSet abstract fun powerBeats3(factory: PowerBeats3.Factory): ApplePodsFactory + @Binds @IntoSet abstract fun powerBeats4(factory: PowerBeats4.Factory): ApplePodsFactory + @Binds @IntoSet abstract fun powerBeatsPro(factory: PowerBeatsPro.Factory): ApplePodsFactory + @Binds @IntoSet abstract fun beatsFitPro(factory: BeatsFitPro.Factory): ApplePodsFactory - @Binds @IntoSet abstract fun fakeAirPodsGen1(factory: FakeAirPodsGen1.Factory): ApplePodsFactory - @Binds @IntoSet abstract fun fakeAirPodsGen2(factory: FakeAirPodsGen2.Factory): ApplePodsFactory - @Binds @IntoSet abstract fun fakeAirPodsGen3(factory: FakeAirPodsGen3.Factory): ApplePodsFactory - @Binds @IntoSet abstract fun fakeAirPodsPro(factory: FakeAirPodsPro.Factory): ApplePodsFactory - @Binds @IntoSet abstract fun fakeAirPodsPro2(factory: FakeAirPodsPro2.Factory): ApplePodsFactory + @Binds @IntoSet abstract fun fakeAirPodsGen1(factory: FakeAirPodsGen1.Factory): ApplePodsFactory + @Binds @IntoSet abstract fun fakeAirPodsGen2(factory: FakeAirPodsGen2.Factory): ApplePodsFactory + @Binds @IntoSet abstract fun fakeAirPodsGen3(factory: FakeAirPodsGen3.Factory): ApplePodsFactory + @Binds @IntoSet abstract fun fakeAirPodsPro(factory: FakeAirPodsPro.Factory): ApplePodsFactory + @Binds @IntoSet abstract fun fakeAirPodsPro2(factory: FakeAirPodsPro2.Factory): ApplePodsFactory } \ 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 092d904c..12da4bbc 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,108 +1,26 @@ package eu.darken.capod.pods.core.apple -import android.R.id.message import eu.darken.capod.common.bluetooth.BleScanResult -import eu.darken.capod.common.bluetooth.BluetoothAddress -import eu.darken.capod.common.collections.median -import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE -import eu.darken.capod.common.debug.logging.Logging.Priority.WARN -import eu.darken.capod.common.debug.logging.log -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.history.KnownDevice 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, -) { +interface ApplePodsFactory { + fun isResponsible(message: ProximityMessage): Boolean + fun create(scanResult: BleScanResult, payload: ProximityPayload): ApplePods - data class Identifier( - val device: UShort, - val podBatteryData: Set, - val caseBatteryData: UShort, - val deviceColor: UByte, + data class ModelInfo( + val full: UShort, + val dirty: UByte, ) - 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(public.data[4].upperNibble, public.data[4].lowerNibble), - caseBatteryData = public.data[5].lowerNibble, - deviceColor = public.data[7] + fun ProximityMessage.getModelInfo(): ModelInfo = ModelInfo( + full = (((data[1].toInt() and 255) shl 8) or (data[2].toInt() and 255)).toUShort(), + dirty = data[1] ) - data class KnownDevice( - val id: PodDevice.Id, - val seenFirstAt: Instant, - val seenCounter: Int, - val history: List, - val lastCaseBattery: Float?, - ) { - val lastPayload: ProximityPayload - get() = history.last().payload - - val lastAddress: BluetoothAddress - get() = history.last().address - - val reliability: Float - get() { - if (history.size < 2) return 0f - - val now = Instant.now() - - val pingInterval = List(history.dropLast(1).size) { index -> - Duration.between(history[index].seenLastAt, history[index + 1].seenLastAt).toMillis() - }.map { it.toInt() }.median() - - val expectedPings: Float = LOOKBACK.toMillis() / pingInterval.toFloat() - - val recentPings = history.filter { - Duration.between(it.seenLastAt, now).toMillis() < LOOKBACK.toMillis() + pingInterval - }.size - - val reliability: Float = (recentPings / expectedPings) - // log("#####") { "interval=$pingInterval, expectedPerMinute=$expectedPings, count=$recentPings" } - - return max(0.0f, reliability).coerceAtMost(1f) - } - - fun rssiSmoothed(latest: Int): Int { - val now = Instant.now() - return history - .filter { Duration.between(it.seenLastAt, now) < LOOKBACK } - .map { it.rssi } - .plus(latest) - .median() - } - - fun isOlderThan(age: Duration): Boolean { - val now = Instant.now() - return Duration.between(history.last().seenLastAt, now) > age - } - - override fun toString(): String = "KnownDevice(history=${history.size}, last=${history.last()})" - - companion object { - // 30 seconds at fastest interval - const val MAX_HISTORY = 60 - val LOOKBACK = Duration.ofSeconds(30) - } - } - - internal val knownDevices = mutableMapOf() - fun KnownDevice.getLatestCaseBattery(): Float? = this.lastCaseBattery - private fun Collection.determineLatestCaseBattery(): Float? = this - .filterIsInstance() - .mapNotNull { it.batteryCasePercent } - .lastOrNull() - fun KnownDevice.getLatestCaseLidState(basic: DualApplePods): DualApplePods.LidState? { val definitive = setOf( DualApplePods.LidState.OPEN, @@ -118,92 +36,4 @@ abstract class ApplePodsFactory( ?.caseLidState ?: DualApplePods.LidState.NOT_IN_CASE } - - open fun historyTrimmer( - pods: List - ): List { - return pods.takeLast(KnownDevice.MAX_HISTORY) - } - - internal open fun searchHistory(current: PodType): KnownDevice? { - val scanResult = current.scanResult - val payload = current.payload - - knownDevices.values.toList().forEach { knownDevice -> - if (knownDevice.isOlderThan(Duration.ofSeconds(30))) { - log(tag, VERBOSE) { "searchHistory1: Removing stale known device: $knownDevice" } - knownDevices.remove(knownDevice.id) - } - } - - knownDevices.values - .filter { it.history.size > KnownDevice.MAX_HISTORY } - .toList() - .forEach { - knownDevices[it.id] = it.copy(history = historyTrimmer(it.history)) - } - - var recognizedDevice: KnownDevice? = knownDevices.values - .firstOrNull { it.lastAddress == scanResult.address } - ?.also { log(tag, VERBOSE) { "searchHistory1: Recovered previous ID via address: $it" } } - -// if() - - if (recognizedDevice == null) { - val currentMarkers = payload.getFuzzyIdentifier() - recognizedDevice = knownDevices.values - .firstOrNull { it.lastPayload.getFuzzyIdentifier() == currentMarkers } - ?.also { log(tag) { "searchHistory1: Close match based on similarity: $currentMarkers" } } - } - - if (recognizedDevice == null) { - log(tag, WARN) { "searchHistory1: Didn't recognize: $message" } - } - - return recognizedDevice - } - - fun updateHistory(device: PodType) { - val existing = knownDevices[device.identifier] - - knownDevices[device.identifier] = when { - existing != null -> { - val history = existing.history.plus(device) - existing.copy( - seenCounter = existing.seenCounter + 1, - history = history, - lastCaseBattery = history.determineLatestCaseBattery() ?: existing.lastCaseBattery - ) - } - - else -> { - log(tag) { "searchHistory1: Creating new history for $device" } - val history = listOf(device) - KnownDevice( - id = device.identifier, - seenFirstAt = device.seenFirstAt, - seenCounter = 1, - history = history, - lastCaseBattery = history.determineLatestCaseBattery() - ) - } - } - } - - data class ModelInfo( - val full: UShort, - val dirty: UByte, - ) - - 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: ProximityMessage): Boolean - - abstract fun create( - scanResult: BleScanResult, - payload: ProximityPayload, - ): ApplePods } \ No newline at end of file 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 deleted file mode 100644 index 9bf266e0..00000000 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/DualApplePodsFactory.kt +++ /dev/null @@ -1,68 +0,0 @@ -package eu.darken.capod.pods.core.apple - -import eu.darken.capod.common.debug.logging.Logging.Priority.DEBUG -import eu.darken.capod.common.debug.logging.log -import eu.darken.capod.pods.core.PodDevice - -abstract class DualApplePodsFactory(private val tag: String) : ApplePodsFactory(tag) { - - fun DualApplePods.getCaseMatchMarkings() = SplitPodsMarkings( - leftPodBattery = batteryLeftPodPercent, - rightPodBattery = batteryRightPodPercent, - microPhoneLeft = isLeftPodMicrophone, - microPhoneRight = isRightPodMicrophone, - chargingLeft = isLeftPodCharging, - chargingRight = isRightPodCharging, - color = pubDeviceColor, - model = model - ) - - /** - * Split pods, one in case, one not. - */ - data class SplitPodsMarkings( - val leftPodBattery: Float?, - val rightPodBattery: Float?, - val microPhoneLeft: Boolean, - val microPhoneRight: Boolean, - val chargingLeft: Boolean, - val chargingRight: Boolean, - val color: UByte, - val model: PodDevice.Model, - ) - - private fun Collection.findSplitPodsMatch(device: DualApplePods): Collection { - val target = device.getCaseMatchMarkings() - - return filter { known -> - known.history - .filterIsInstance() - .any { it.getCaseMatchMarkings() == target } - } - } - - override fun searchHistory(current: DualApplePods): KnownDevice? { - val basicResult = super.searchHistory(current) - - val caseIgnored = knownDevices.values.findSplitPodsMatch(current) - - log(tag, DEBUG) { "searchHistory2: Case ignored matches(${caseIgnored.size}): $caseIgnored" } - - return when (caseIgnored.size) { - 0 -> basicResult - 1 -> caseIgnored.single() - else -> { - log(tag) { "searchHistory2: More than one result when ignoring case markers." } - val oldest = caseIgnored.maxByOrNull { it.history.size } ?: return null - - caseIgnored.minus(oldest).forEach { - log(tag) { "searchHistory2: Removing outlier: $it" } - knownDevices.remove(it.id) - } - - oldest - } - } - } - -} \ No newline at end of file diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/SingleApplePodsFactory.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/SingleApplePodsFactory.kt deleted file mode 100644 index c1824edd..00000000 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/SingleApplePodsFactory.kt +++ /dev/null @@ -1,3 +0,0 @@ -package eu.darken.capod.pods.core.apple - -abstract class SingleApplePodsFactory(private val tag: String) : ApplePodsFactory(tag) \ No newline at end of file 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 56beb10c..fa5c1653 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 @@ -4,8 +4,9 @@ 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.ApplePodsFactory import eu.darken.capod.pods.core.apple.DualApplePods -import eu.darken.capod.pods.core.apple.DualApplePodsFactory +import eu.darken.capod.pods.core.apple.history.PodHistoryRepo 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 @@ -36,7 +37,9 @@ data class AirPodsGen1( override val rssi: Int get() = rssiAverage ?: super.rssi - class Factory @Inject constructor() : DualApplePodsFactory(TAG) { + class Factory @Inject constructor( + private val repo: PodHistoryRepo, + ) : ApplePodsFactory { override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH @@ -47,10 +50,10 @@ data class AirPodsGen1( payload: ProximityPayload ): ApplePods { var basic = AirPodsGen1(scanResult = scanResult, payload = payload) - val result = searchHistory(basic) + val result = repo.search(basic) if (result != null) basic = basic.copy(identifier = result.id) - updateHistory(basic) + repo.updateHistory(basic) if (result == null) return basic 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 2a411297..1f6650ca 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 @@ -4,8 +4,9 @@ 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.ApplePodsFactory import eu.darken.capod.pods.core.apple.DualApplePods -import eu.darken.capod.pods.core.apple.DualApplePodsFactory +import eu.darken.capod.pods.core.apple.history.PodHistoryRepo 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 @@ -36,7 +37,9 @@ data class AirPodsGen2( override val rssi: Int get() = rssiAverage ?: super.rssi - class Factory @Inject constructor() : DualApplePodsFactory(TAG) { + class Factory @Inject constructor( + private val repo: PodHistoryRepo + ) : ApplePodsFactory { override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH @@ -47,10 +50,10 @@ data class AirPodsGen2( payload: ProximityPayload ): ApplePods { var basic = AirPodsGen2(scanResult = scanResult, payload = payload) - val result = searchHistory(basic) + val result = repo.search(basic) if (result != null) basic = basic.copy(identifier = result.id) - updateHistory(basic) + repo.updateHistory(basic) if (result == null) return basic 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 50180d77..68f7b978 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 @@ -4,8 +4,9 @@ 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.ApplePodsFactory import eu.darken.capod.pods.core.apple.DualApplePods -import eu.darken.capod.pods.core.apple.DualApplePodsFactory +import eu.darken.capod.pods.core.apple.history.PodHistoryRepo 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 @@ -36,7 +37,9 @@ data class AirPodsGen3( override val rssi: Int get() = rssiAverage ?: super.rssi - class Factory @Inject constructor() : DualApplePodsFactory(TAG) { + class Factory @Inject constructor( + private val repo: PodHistoryRepo, + ) : ApplePodsFactory { override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH @@ -47,10 +50,10 @@ data class AirPodsGen3( payload: ProximityPayload ): ApplePods { var basic = AirPodsGen3(scanResult = scanResult, payload = payload) - val result = searchHistory(basic) + val result = repo.search(basic) if (result != null) basic = basic.copy(identifier = result.id) - updateHistory(basic) + repo.updateHistory(basic) if (result == null) return basic 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 2be39b84..3513917c 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 @@ -4,8 +4,9 @@ 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.ApplePodsFactory import eu.darken.capod.pods.core.apple.DualApplePods -import eu.darken.capod.pods.core.apple.DualApplePodsFactory +import eu.darken.capod.pods.core.apple.history.PodHistoryRepo 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 @@ -36,7 +37,9 @@ data class AirPodsGen4( override val rssi: Int get() = rssiAverage ?: super.rssi - class Factory @Inject constructor() : DualApplePodsFactory(TAG) { + class Factory @Inject constructor( + private val repo: PodHistoryRepo, + ) : ApplePodsFactory { override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH @@ -47,10 +50,10 @@ data class AirPodsGen4( payload: ProximityPayload ): ApplePods { var basic = AirPodsGen4(scanResult = scanResult, payload = payload) - val result = searchHistory(basic) + val result = repo.search(basic) if (result != null) basic = basic.copy(identifier = result.id) - updateHistory(basic) + repo.updateHistory(basic) if (result == null) return basic 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 e7c94095..75445db4 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 @@ -4,8 +4,9 @@ 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.ApplePodsFactory import eu.darken.capod.pods.core.apple.DualApplePods -import eu.darken.capod.pods.core.apple.DualApplePodsFactory +import eu.darken.capod.pods.core.apple.history.PodHistoryRepo 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 @@ -36,7 +37,9 @@ data class AirPodsGen4Anc( override val rssi: Int get() = rssiAverage ?: super.rssi - class Factory @Inject constructor() : DualApplePodsFactory(TAG) { + class Factory @Inject constructor( + private val repo: PodHistoryRepo, + ) : ApplePodsFactory { override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH @@ -47,10 +50,10 @@ data class AirPodsGen4Anc( payload: ProximityPayload ): ApplePods { var basic = AirPodsGen4Anc(scanResult = scanResult, payload = payload) - val result = searchHistory(basic) + val result = repo.search(basic) if (result != null) basic = basic.copy(identifier = result.id) - updateHistory(basic) + repo.updateHistory(basic) if (result == null) return basic 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 a7a1c651..0e8efd39 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 @@ -7,9 +7,10 @@ import eu.darken.capod.pods.core.HasChargeDetection import eu.darken.capod.pods.core.HasEarDetection import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.apple.ApplePods +import eu.darken.capod.pods.core.apple.ApplePodsFactory 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.history.PodHistoryRepo 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 @@ -38,7 +39,9 @@ data class AirPodsMax( override val isBeingWorn: Boolean get() = pubStatus.isBitSet(5) - class Factory @Inject constructor() : SingleApplePodsFactory(TAG) { + class Factory @Inject constructor( + private val repo: PodHistoryRepo, + ) : ApplePodsFactory { override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH @@ -49,10 +52,10 @@ data class AirPodsMax( payload: ProximityPayload ): ApplePods { var basic = AirPodsMax(scanResult = scanResult, payload = payload) - val result = searchHistory(basic) + val result = repo.search(basic) if (result != null) basic = basic.copy(identifier = result.id) - updateHistory(basic) + repo.updateHistory(basic) if (result == null) return basic 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 fab35f8b..927f9903 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 @@ -7,9 +7,10 @@ import eu.darken.capod.pods.core.HasChargeDetection import eu.darken.capod.pods.core.HasEarDetection import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.apple.ApplePods +import eu.darken.capod.pods.core.apple.ApplePodsFactory 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.history.PodHistoryRepo 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 @@ -38,7 +39,9 @@ data class AirPodsMaxUsbc( override val isBeingWorn: Boolean get() = pubStatus.isBitSet(5) - class Factory @Inject constructor() : SingleApplePodsFactory(TAG) { + class Factory @Inject constructor( + private val repo: PodHistoryRepo, + ) : ApplePodsFactory { override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH @@ -49,10 +52,10 @@ data class AirPodsMaxUsbc( payload: ProximityPayload ): ApplePods { var basic = AirPodsMaxUsbc(scanResult = scanResult, payload = payload) - val result = searchHistory(basic) + val result = repo.search(basic) if (result != null) basic = basic.copy(identifier = result.id) - updateHistory(basic) + repo.updateHistory(basic) if (result == null) return basic 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 dfd2b19a..d38ef4e2 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 @@ -6,9 +6,10 @@ 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.ApplePodsFactory 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.history.PodHistoryRepo 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 @@ -55,7 +56,9 @@ data class AirPodsPro( override val rssi: Int get() = rssiAverage ?: super.rssi - class Factory @Inject constructor() : DualApplePodsFactory(TAG) { + class Factory @Inject constructor( + private val repo: PodHistoryRepo, + ) : ApplePodsFactory { override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH @@ -69,10 +72,10 @@ data class AirPodsPro( scanResult = scanResult, payload = payload, ) - val result = searchHistory(basic) + val result = repo.search(basic) if (result != null) basic = basic.copy(identifier = result.id) - updateHistory(basic) + repo.updateHistory(basic) if (result == null) return 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 509fda2d..1aaef713 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 @@ -6,9 +6,10 @@ 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.ApplePodsFactory 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.history.PodHistoryRepo 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 @@ -55,7 +56,9 @@ data class AirPodsPro2( override val rssi: Int get() = rssiAverage ?: super.rssi - class Factory @Inject constructor() : DualApplePodsFactory(TAG) { + class Factory @Inject constructor( + private val repo: PodHistoryRepo, + ) : ApplePodsFactory { override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH @@ -66,10 +69,10 @@ data class AirPodsPro2( payload: ProximityPayload ): ApplePods { var basic = AirPodsPro2(scanResult = scanResult, payload = payload) - val result = searchHistory(basic) + val result = repo.search(basic) if (result != null) basic = basic.copy(identifier = result.id) - updateHistory(basic) + repo.updateHistory(basic) if (result == null) return basic 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 50cf07d1..4fb85594 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 @@ -6,9 +6,10 @@ 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.ApplePodsFactory 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.history.PodHistoryRepo 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 @@ -55,7 +56,9 @@ data class AirPodsPro2Usbc( override val rssi: Int get() = rssiAverage ?: super.rssi - class Factory @Inject constructor() : DualApplePodsFactory(TAG) { + class Factory @Inject constructor( + private val repo: PodHistoryRepo, + ) : ApplePodsFactory { override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH @@ -66,10 +69,10 @@ data class AirPodsPro2Usbc( payload: ProximityPayload ): ApplePods { var basic = AirPodsPro2Usbc(scanResult = scanResult, payload = payload) - val result = searchHistory(basic) + val result = repo.search(basic) if (result != null) basic = basic.copy(identifier = result.id) - updateHistory(basic) + repo.updateHistory(basic) if (result == null) return basic 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 aa2b5f59..654ffca7 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 @@ -4,8 +4,9 @@ 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.ApplePodsFactory import eu.darken.capod.pods.core.apple.DualApplePods -import eu.darken.capod.pods.core.apple.DualApplePodsFactory +import eu.darken.capod.pods.core.apple.history.PodHistoryRepo 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 @@ -36,7 +37,9 @@ data class BeatsFitPro( override val rssi: Int get() = rssiAverage ?: super.rssi - class Factory @Inject constructor() : DualApplePodsFactory(TAG) { + class Factory @Inject constructor( + private val repo: PodHistoryRepo, + ) : ApplePodsFactory { override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH @@ -47,10 +50,10 @@ data class BeatsFitPro( payload: ProximityPayload ): ApplePods { var basic = BeatsFitPro(scanResult = scanResult, payload = payload) - val result = searchHistory(basic) + val result = repo.search(basic) if (result != null) basic = basic.copy(identifier = result.id) - updateHistory(basic) + repo.updateHistory(basic) if (result == null) return basic 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 fca06a6a..06ed7352 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 @@ -4,8 +4,9 @@ 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.ApplePodsFactory import eu.darken.capod.pods.core.apple.SingleApplePods -import eu.darken.capod.pods.core.apple.SingleApplePodsFactory +import eu.darken.capod.pods.core.apple.history.PodHistoryRepo 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 @@ -28,7 +29,9 @@ data class BeatsFlex( override val rssi: Int get() = rssiAverage ?: super.rssi - class Factory @Inject constructor() : SingleApplePodsFactory(TAG) { + class Factory @Inject constructor( + private val repo: PodHistoryRepo, + ) : ApplePodsFactory { override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH @@ -39,10 +42,10 @@ data class BeatsFlex( payload: ProximityPayload ): ApplePods { var basic = BeatsFlex(scanResult = scanResult, payload = payload) - val result = searchHistory(basic) + val result = repo.search(basic) if (result != null) basic = basic.copy(identifier = result.id) - updateHistory(basic) + repo.updateHistory(basic) if (result == null) return basic 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 e38fcbbc..7c0734ad 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 @@ -4,8 +4,9 @@ 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.ApplePodsFactory import eu.darken.capod.pods.core.apple.SingleApplePods -import eu.darken.capod.pods.core.apple.SingleApplePodsFactory +import eu.darken.capod.pods.core.apple.history.PodHistoryRepo 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 @@ -27,7 +28,9 @@ data class BeatsSolo3( override val rssi: Int get() = rssiAverage ?: super.rssi - class Factory @Inject constructor() : SingleApplePodsFactory(TAG) { + class Factory @Inject constructor( + private val repo: PodHistoryRepo, + ) : ApplePodsFactory { override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH @@ -38,10 +41,10 @@ data class BeatsSolo3( payload: ProximityPayload ): ApplePods { var basic = BeatsSolo3(scanResult = scanResult, payload = payload) - val result = searchHistory(basic) + val result = repo.search(basic) if (result != null) basic = basic.copy(identifier = result.id) - updateHistory(basic) + repo.updateHistory(basic) if (result == null) return basic 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 773f20b8..9914ec1a 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 @@ -4,8 +4,9 @@ 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.ApplePodsFactory import eu.darken.capod.pods.core.apple.SingleApplePods -import eu.darken.capod.pods.core.apple.SingleApplePodsFactory +import eu.darken.capod.pods.core.apple.history.PodHistoryRepo 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 @@ -25,7 +26,9 @@ data class BeatsStudio3( override val model: PodDevice.Model = PodDevice.Model.BEATS_STUDIO_3 - class Factory @Inject constructor() : SingleApplePodsFactory(TAG) { + class Factory @Inject constructor( + private val repo: PodHistoryRepo, + ) : ApplePodsFactory { override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().dirty == DEVICE_CODE_DIRTY && length == ProximityPairing.PAIRING_MESSAGE_LENGTH @@ -36,10 +39,10 @@ data class BeatsStudio3( payload: ProximityPayload ): ApplePods { var basic = BeatsStudio3(scanResult = scanResult, payload = payload) - val result = searchHistory(basic) + val result = repo.search(basic) if (result != null) basic = basic.copy(identifier = result.id) - updateHistory(basic) + repo.updateHistory(basic) if (result == null) return basic 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 12238c88..21936969 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 @@ -4,8 +4,9 @@ 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.ApplePodsFactory import eu.darken.capod.pods.core.apple.SingleApplePods -import eu.darken.capod.pods.core.apple.SingleApplePodsFactory +import eu.darken.capod.pods.core.apple.history.PodHistoryRepo 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 @@ -28,7 +29,9 @@ data class BeatsX( override val rssi: Int get() = rssiAverage ?: super.rssi - class Factory @Inject constructor() : SingleApplePodsFactory(TAG) { + class Factory @Inject constructor( + private val repo: PodHistoryRepo, + ) : ApplePodsFactory { override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH @@ -39,10 +42,10 @@ data class BeatsX( payload: ProximityPayload ): ApplePods { var basic = BeatsX(scanResult = scanResult, payload = payload) - val result = searchHistory(basic) + val result = repo.search(basic) if (result != null) basic = basic.copy(identifier = result.id) - updateHistory(basic) + repo.updateHistory(basic) if (result == null) return basic 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 c1972522..fe210ad2 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 @@ -4,8 +4,9 @@ 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.ApplePodsFactory import eu.darken.capod.pods.core.apple.SingleApplePods -import eu.darken.capod.pods.core.apple.SingleApplePodsFactory +import eu.darken.capod.pods.core.apple.history.PodHistoryRepo 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 @@ -28,7 +29,9 @@ data class PowerBeats3( override val rssi: Int get() = rssiAverage ?: super.rssi - class Factory @Inject constructor() : SingleApplePodsFactory(TAG) { + class Factory @Inject constructor( + private val repo: PodHistoryRepo, + ) : ApplePodsFactory { override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH @@ -39,10 +42,10 @@ data class PowerBeats3( payload: ProximityPayload ): ApplePods { var basic = PowerBeats3(scanResult = scanResult, payload = payload) - val result = searchHistory(basic) + val result = repo.search(basic) if (result != null) basic = basic.copy(identifier = result.id) - updateHistory(basic) + repo.updateHistory(basic) if (result == null) return basic 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 460f4ac3..62ae4b36 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 @@ -4,9 +4,10 @@ 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.ApplePodsFactory 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.history.PodHistoryRepo 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 @@ -29,7 +30,9 @@ data class PowerBeats4( override val rssi: Int get() = rssiAverage ?: super.rssi - class Factory @Inject constructor() : SingleApplePodsFactory(TAG) { + class Factory @Inject constructor( + private val repo: PodHistoryRepo, + ) : ApplePodsFactory { override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH @@ -40,10 +43,10 @@ data class PowerBeats4( payload: ProximityPayload ): ApplePods { var basic = PowerBeats4(scanResult = scanResult, payload = payload) - val result = searchHistory(basic) + val result = repo.search(basic) if (result != null) basic = basic.copy(identifier = result.id) - updateHistory(basic) + repo.updateHistory(basic) if (result == null) return basic 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 ddf5d305..0fcf0287 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 @@ -4,8 +4,9 @@ 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.ApplePodsFactory import eu.darken.capod.pods.core.apple.DualApplePods -import eu.darken.capod.pods.core.apple.DualApplePodsFactory +import eu.darken.capod.pods.core.apple.history.PodHistoryRepo 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 @@ -36,7 +37,9 @@ data class PowerBeatsPro( override val rssi: Int get() = rssiAverage ?: super.rssi - class Factory @Inject constructor() : DualApplePodsFactory(TAG) { + class Factory @Inject constructor( + private val repo: PodHistoryRepo, + ) : ApplePodsFactory { override fun isResponsible(message: ProximityMessage): Boolean = message.run { getModelInfo().dirty == DEVICE_CODE_DIRTY && length == ProximityPairing.PAIRING_MESSAGE_LENGTH @@ -47,10 +50,10 @@ data class PowerBeatsPro( payload: ProximityPayload ): ApplePods { var basic = PowerBeatsPro(scanResult = scanResult, payload = payload) - val result = searchHistory(basic) + val result = repo.search(basic) if (result != null) basic = basic.copy(identifier = result.id) - updateHistory(basic) + repo.updateHistory(basic) if (result == null) return basic diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/history/KnownDevice.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/history/KnownDevice.kt new file mode 100644 index 00000000..70e4cff3 --- /dev/null +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/history/KnownDevice.kt @@ -0,0 +1,68 @@ +package eu.darken.capod.pods.core.apple.history + +import eu.darken.capod.common.bluetooth.BluetoothAddress +import eu.darken.capod.common.collections.median +import eu.darken.capod.pods.core.PodDevice +import eu.darken.capod.pods.core.apple.ApplePods +import eu.darken.capod.pods.core.apple.protocol.ProximityPayload +import java.time.Duration +import java.time.Instant +import kotlin.math.max + +data class KnownDevice( + val id: PodDevice.Id, + val seenFirstAt: Instant, + val seenCounter: Int, + val history: List, + val lastCaseBattery: Float?, +) { + val lastPayload: ProximityPayload + get() = history.last().payload + + val lastAddress: BluetoothAddress + get() = history.last().address + + val reliability: Float + get() { + if (history.size < 2) return 0f + + val now = Instant.now() + + val pingInterval = List(history.dropLast(1).size) { index -> + Duration.between(history[index].seenLastAt, history[index + 1].seenLastAt).toMillis() + }.map { it.toInt() }.median() + + val expectedPings: Float = LOOKBACK.toMillis() / pingInterval.toFloat() + + val recentPings = history.filter { + Duration.between(it.seenLastAt, now).toMillis() < LOOKBACK.toMillis() + pingInterval + }.size + + val reliability: Float = (recentPings / expectedPings) + // log("#####") { "interval=$pingInterval, expectedPerMinute=$expectedPings, count=$recentPings" } + + return max(0.0f, reliability).coerceAtMost(1f) + } + + fun rssiSmoothed(latest: Int): Int { + val now = Instant.now() + return history + .filter { Duration.between(it.seenLastAt, now) < LOOKBACK } + .map { it.rssi } + .plus(latest) + .median() + } + + fun isOlderThan(age: Duration): Boolean { + val now = Instant.now() + return Duration.between(history.last().seenLastAt, now) > age + } + + override fun toString(): String = "KnownDevice(history=${history.size}, last=${history.last()})" + + companion object { + // 30 seconds at fastest interval + const val MAX_HISTORY = 60 + val LOOKBACK = Duration.ofSeconds(30) + } +} \ No newline at end of file diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/history/PodHistoryRepo.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/history/PodHistoryRepo.kt new file mode 100644 index 00000000..7a806f49 --- /dev/null +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/history/PodHistoryRepo.kt @@ -0,0 +1,178 @@ +package eu.darken.capod.pods.core.apple.history + +import eu.darken.capod.common.debug.logging.Logging.Priority.DEBUG +import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE +import eu.darken.capod.common.debug.logging.Logging.Priority.WARN +import eu.darken.capod.common.debug.logging.log +import eu.darken.capod.common.debug.logging.logTag +import eu.darken.capod.common.lowerNibble +import eu.darken.capod.common.upperNibble +import eu.darken.capod.main.core.GeneralSettings +import eu.darken.capod.pods.core.HasCase +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.protocol.ProximityPayload +import eu.darken.capod.pods.core.apple.protocol.RPAChecker +import java.time.Duration +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class PodHistoryRepo @Inject constructor( + private val generalSettings: GeneralSettings, + private val rpaChecker: RPAChecker, +) { + + private val knownDevices = mutableMapOf() + + data class Identifier( + val device: UShort, + val podBatteryData: Set, + val caseBatteryData: UShort, + val deviceColor: UByte, + ) + + 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(public.data[4].upperNibble, public.data[4].lowerNibble), + caseBatteryData = public.data[5].lowerNibble, + deviceColor = public.data[7] + ) + + fun DualApplePods.getCaseMatchMarkings() = SplitPodsMarkings( + leftPodBattery = batteryLeftPodPercent, + rightPodBattery = batteryRightPodPercent, + microPhoneLeft = isLeftPodMicrophone, + microPhoneRight = isRightPodMicrophone, + chargingLeft = isLeftPodCharging, + chargingRight = isRightPodCharging, + color = pubDeviceColor, + model = model + ) + + /** + * Split pods, one in case, one not. + */ + data class SplitPodsMarkings( + val leftPodBattery: Float?, + val rightPodBattery: Float?, + val microPhoneLeft: Boolean, + val microPhoneRight: Boolean, + val chargingLeft: Boolean, + val chargingRight: Boolean, + val color: UByte, + val model: PodDevice.Model, + ) + + fun search(current: ApplePods): KnownDevice? { + val basicResult = baseSearch(current) + + val caseIgnored = if (current is DualApplePods) { + val target = current.getCaseMatchMarkings() + knownDevices.values.filter { known -> + known.history.filterIsInstance().any { it.getCaseMatchMarkings() == target } + } + } else { + emptySet() + } + + log(TAG, DEBUG) { "search2: Case ignored matches(${caseIgnored.size}): $caseIgnored" } + + return when (caseIgnored.size) { + 0 -> basicResult + 1 -> caseIgnored.single() + else -> { + log(TAG) { "search2: More than one result when ignoring case markers." } + val oldest = caseIgnored.maxByOrNull { it.history.size } ?: return null + + caseIgnored.minus(oldest).forEach { + log(TAG) { "search2: Removing outlier: $it" } + knownDevices.remove(it.id) + } + + oldest + } + } + } + + private fun baseSearch(current: ApplePods): KnownDevice? { + val scanResult = current.scanResult + val payload = current.payload + + // Timeout + knownDevices.values.toList() + .filter { it.isOlderThan(Duration.ofSeconds(30)) } + .forEach { knownDevice -> + log(TAG, VERBOSE) { "search1: Removing stale known device: $knownDevice" } + knownDevices.remove(knownDevice.id) + } + + // Trim history + knownDevices.values + .filter { it.history.size > KnownDevice.MAX_HISTORY } + .toList() + .forEach { knownDevices[it.id] = it.copy(history = it.history.takeLast(KnownDevice.MAX_HISTORY)) } + + var recognizedDevice: KnownDevice? = knownDevices.values + .firstOrNull { it.lastAddress == scanResult.address } + ?.also { log(TAG, VERBOSE) { "search1: Recovered previous ID via address: $it" } } + + if (recognizedDevice == null) { + generalSettings.mainDeviceIdentityKey.value?.let { key -> + if (!rpaChecker.verify(current.address, key)) { + log(TAG, VERBOSE) { "search1: Current device does not match IRK" } + return@let + } + + recognizedDevice = knownDevices.values + .firstOrNull { rpaChecker.verify(it.lastAddress, key) } + .also { log(TAG, VERBOSE) { "search1: Recovered previous ID via IRK: $it" } } + } + } + + if (recognizedDevice == null) { + val currentMarkers = payload.getFuzzyIdentifier() + recognizedDevice = knownDevices.values + .firstOrNull { it.lastPayload.getFuzzyIdentifier() == currentMarkers } + ?.also { log(TAG) { "search1: Close match based on similarity: $currentMarkers" } } + } + + if (recognizedDevice == null) log(TAG, WARN) { "search1: Didn't recognize: $current" } + + return recognizedDevice + } + + fun updateHistory(device: ApplePods) { + val existing = knownDevices[device.identifier] + + fun Collection.determineLatestCaseBattery(): Float? = this + .filterIsInstance() + .mapNotNull { it.batteryCasePercent } + .lastOrNull() + + knownDevices[device.identifier] = if (existing != null) { + val history = existing.history.plus(device) + existing.copy( + seenCounter = existing.seenCounter + 1, + history = history, + lastCaseBattery = history.determineLatestCaseBattery() ?: existing.lastCaseBattery + ) + } else { + log(TAG) { "Creating new history for $device" } + val history = listOf(device) + KnownDevice( + id = device.identifier, + seenFirstAt = device.seenFirstAt, + seenCounter = 1, + history = history, + lastCaseBattery = history.determineLatestCaseBattery() + ) + } + } + + companion object { + private val TAG = logTag("Pod", "Apple", "Factory", "History") + } +} \ No newline at end of file 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 9c6eac8f..62b85ad0 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 @@ -9,6 +9,7 @@ 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.history.PodHistoryRepo import eu.darken.capod.pods.core.apple.protocol.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant @@ -39,7 +40,9 @@ data class FakeAirPodsGen1( override val rssi: Int get() = rssiAverage ?: super.rssi - class Factory @Inject constructor() : ApplePodsFactory(TAG) { + class Factory @Inject constructor( + private val repo: PodHistoryRepo, + ) : ApplePodsFactory { override fun isResponsible(message: ProximityMessage): Boolean = message.run { // Official message length is 19HEX, i.e. binary 25, did they copy this wrong? @@ -51,10 +54,10 @@ data class FakeAirPodsGen1( payload: ProximityPayload ): ApplePods { var basic = FakeAirPodsGen1(scanResult = scanResult, payload = payload) - val result = searchHistory(basic) + val result = repo.search(basic) if (result != null) basic = basic.copy(identifier = result.id) - updateHistory(basic) + repo.updateHistory(basic) if (result == null) return basic 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 a6095ba7..0c73f368 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 @@ -9,6 +9,7 @@ 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.history.PodHistoryRepo import eu.darken.capod.pods.core.apple.protocol.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant @@ -37,7 +38,9 @@ data class FakeAirPodsGen2( override val batteryCasePercent: Float? get() = super.batteryCasePercent ?: cachedBatteryPercentage - class Factory @Inject constructor() : ApplePodsFactory(TAG) { + class Factory @Inject constructor( + private val repo: PodHistoryRepo, + ) : ApplePodsFactory { override fun isResponsible(message: ProximityMessage): Boolean = message.run { // Official message length is 19HEX, i.e. binary 25, did they copy this wrong? @@ -49,10 +52,10 @@ data class FakeAirPodsGen2( payload: ProximityPayload ): ApplePods { var basic = FakeAirPodsGen2(scanResult = scanResult, payload = payload) - val result = searchHistory(basic) + val result = repo.search(basic) if (result != null) basic = basic.copy(identifier = result.id) - updateHistory(basic) + repo.updateHistory(basic) if (result == null) return basic 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 c27a6334..0ce21dc8 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 @@ -9,6 +9,7 @@ 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.history.PodHistoryRepo import eu.darken.capod.pods.core.apple.protocol.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant @@ -39,7 +40,9 @@ data class FakeAirPodsGen3( override val batteryCasePercent: Float? get() = super.batteryCasePercent ?: cachedBatteryPercentage - class Factory @Inject constructor() : ApplePodsFactory(TAG) { + class Factory @Inject constructor( + private val repo: PodHistoryRepo, + ) : ApplePodsFactory { override fun isResponsible(message: ProximityMessage): Boolean = message.run { // Official message length is 19HEX, i.e. binary 25, did they copy this wrong? @@ -51,10 +54,10 @@ data class FakeAirPodsGen3( payload: ProximityPayload ): ApplePods { var basic = FakeAirPodsGen3(scanResult = scanResult, payload = payload) - val result = searchHistory(basic) + val result = repo.search(basic) if (result != null) basic = basic.copy(identifier = result.id) - updateHistory(basic) + repo.updateHistory(basic) if (result == null) return basic 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 9f8d79b0..1ef50214 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 @@ -11,6 +11,7 @@ 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.history.PodHistoryRepo import eu.darken.capod.pods.core.apple.protocol.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant @@ -57,7 +58,9 @@ data class FakeAirPodsPro( override val batteryCasePercent: Float? get() = super.batteryCasePercent ?: cachedBatteryPercentage - class Factory @Inject constructor() : ApplePodsFactory(TAG) { + class Factory @Inject constructor( + private val repo: PodHistoryRepo, + ) : ApplePodsFactory { override fun isResponsible(message: ProximityMessage): Boolean = message.run { // Official message length is 19HEX, i.e. binary 25, did they copy this wrong? @@ -69,10 +72,10 @@ data class FakeAirPodsPro( payload: ProximityPayload ): ApplePods { var basic = FakeAirPodsPro(scanResult = scanResult, payload = payload) - val result = searchHistory(basic) + val result = repo.search(basic) if (result != null) basic = basic.copy(identifier = result.id) - updateHistory(basic) + repo.updateHistory(basic) if (result == null) return basic 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 61600472..ea699139 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 @@ -13,6 +13,7 @@ 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.DualApplePods.LidState +import eu.darken.capod.pods.core.apple.history.PodHistoryRepo import eu.darken.capod.pods.core.apple.protocol.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant @@ -61,7 +62,9 @@ data class FakeAirPodsPro2( override val batteryCasePercent: Float? get() = super.batteryCasePercent ?: cachedBatteryPercentage - class Factory @Inject constructor() : ApplePodsFactory(TAG) { + class Factory @Inject constructor( + private val repo: PodHistoryRepo, + ) : ApplePodsFactory { override fun isResponsible(message: ProximityMessage): Boolean = message.run { // Official message length is 19HEX, i.e. binary 25, did they copy this wrong? @@ -73,10 +76,10 @@ data class FakeAirPodsPro2( payload: ProximityPayload ): ApplePods { var basic = FakeAirPodsPro2(scanResult = scanResult, payload = payload) - val result = searchHistory(basic) + val result = repo.search(basic) if (result != null) basic = basic.copy(identifier = result.id) - updateHistory(basic) + repo.updateHistory(basic) if (result == null) return basic 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 2cd3824b..6fabf9f0 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,6 +7,7 @@ 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.history.PodHistoryRepo import eu.darken.capod.pods.core.apple.protocol.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant @@ -30,7 +31,9 @@ data class UnknownAppleDevice( override val rssi: Int get() = rssiAverage ?: super.rssi - class Factory @Inject constructor() : ApplePodsFactory(TAG) { + class Factory @Inject constructor( + private val repo: PodHistoryRepo, + ) : ApplePodsFactory { override fun isResponsible(message: ProximityMessage): Boolean = true override fun create( @@ -38,10 +41,10 @@ data class UnknownAppleDevice( payload: ProximityPayload, ): ApplePods { var basic = UnknownAppleDevice(scanResult = scanResult, payload = payload) - val result = searchHistory(basic) + val result = repo.search(basic) if (result != null) basic = basic.copy(identifier = result.id) - updateHistory(basic) + repo.updateHistory(basic) if (result == null) return basic