Introduce ApplePods.Flags and UI for key status

This commit introduces a new `Flags` data class within `ApplePods` to store boolean flags related to the device, specifically `isIRKMatch`.

This flag is now passed during the creation of `ApplePods` instances in all `ApplePodsFactory` implementations and the central `AppleFactory`.

The UI has been updated to display a key icon in the overview cards (`DualPodsCardVH` and `SinglePodsCardVH`):
- The icon is only visible if `flags.isIRKMatch` is true.
- The icon changes to a filled key (`ic_key_24`) if `payload.private` is not null (indicating successful decryption), otherwise an outline key (`ic_key_outline_24`) is shown.
- Minor layout adjustments were made in `overview_pods_single_item.xml` and `overview_pods_dual_item.xml` to accommodate the new key icon.
This commit is contained in:
darken
2025-06-06 06:03:41 +02:00
committed by Matthias Urhahn
parent eab0296125
commit ffe8a58746
31 changed files with 198 additions and 84 deletions
@@ -58,38 +58,40 @@ class AppleFactory @Inject constructor(
suspend fun create(scanResult: BleScanResult): PodDevice? = lock.withLock {
val proximityMessage = getMessage(scanResult) ?: return@withLock null
val payload = getPayload(scanResult, proximityMessage)
val isIrkMatch = generalSettings.mainDeviceIdentityKey.value
?.let { rpaChecker.verify(scanResult.address, it) }
?: false
val payload = ProximityPayload(
public = ProximityPayload.Public(
proximityMessage.data.take(16).toUByteArray()
),
private = run {
if (!isIrkMatch) return@run null
val encKey = generalSettings.mainDeviceEncryptionKey.value
if (encKey == null) return@run null
val encrypted = proximityMessage.data.takeLast(16).toUByteArray().toByteArray()
proximityMessageDecrypter.decrypt(encrypted, encKey)?.let {
ProximityPayload.Private(data = it)
}
},
)
val factory = podFactories.firstOrNull { it.isResponsible(proximityMessage) }
return@withLock (factory ?: unknownAppleFactory).create(
scanResult = scanResult,
payload = payload,
flags = ApplePods.Flags(
isIRKMatch = true,
),
)
}
private fun getPayload(
scanResult: BleScanResult,
proximityMessage: ProximityMessage
) = ProximityPayload(
public = ProximityPayload.Public(
proximityMessage.data.take(16).toUByteArray()
),
private = run {
val irkKey = generalSettings.mainDeviceIdentityKey.value
if (irkKey == null) return@run null
val encKey = generalSettings.mainDeviceEncryptionKey.value
if (encKey == null) return@run null
if (!rpaChecker.verify(scanResult.address, irkKey)) return@run null
val encrypted = proximityMessage.data.takeLast(16).toUByteArray().toByteArray()
proximityMessageDecrypter.decrypt(encrypted, encKey)?.let {
ProximityPayload.Private(data = it)
}
},
)
companion object {
private val TAG = logTag("Pod", "Apple", "Factory")
}
@@ -8,6 +8,7 @@ import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
interface ApplePods : PodDevice {
val payload: ProximityPayload
val flags: Flags
// We start counting at the airpods prefix byte
val pubPrefix: UByte
@@ -54,4 +55,8 @@ interface ApplePods : PodDevice {
level = level
)
}
data class Flags(
val isIRKMatch: Boolean,
)
}
@@ -7,7 +7,7 @@ import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
interface ApplePodsFactory {
fun isResponsible(message: ProximityMessage): Boolean
fun create(scanResult: BleScanResult, payload: ProximityPayload): ApplePods
fun create(scanResult: BleScanResult, payload: ProximityPayload, flags: ApplePods.Flags): ApplePods
data class ModelInfo(
val full: UShort,
@@ -20,6 +20,7 @@ data class AirPodsGen1(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
override val flags: ApplePods.Flags,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -47,9 +48,14 @@ data class AirPodsGen1(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload
payload: ProximityPayload,
flags: ApplePods.Flags
): ApplePods {
var basic = AirPodsGen1(scanResult = scanResult, payload = payload)
var basic = AirPodsGen1(
scanResult = scanResult,
payload = payload,
flags = flags,
)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -20,6 +20,7 @@ data class AirPodsGen2(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
override val flags: ApplePods.Flags,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -47,9 +48,14 @@ data class AirPodsGen2(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload
payload: ProximityPayload,
flags: ApplePods.Flags
): ApplePods {
var basic = AirPodsGen2(scanResult = scanResult, payload = payload)
var basic = AirPodsGen2(
scanResult = scanResult,
payload = payload,
flags = flags,
)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -20,6 +20,7 @@ data class AirPodsGen3(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
override val flags: ApplePods.Flags,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -47,9 +48,10 @@ data class AirPodsGen3(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload
payload: ProximityPayload,
flags: ApplePods.Flags
): ApplePods {
var basic = AirPodsGen3(scanResult = scanResult, payload = payload)
var basic = AirPodsGen3(scanResult = scanResult, payload = payload, flags = flags)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -20,6 +20,7 @@ data class AirPodsGen4(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
override val flags: ApplePods.Flags,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -47,9 +48,10 @@ data class AirPodsGen4(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload
payload: ProximityPayload,
flags: ApplePods.Flags
): ApplePods {
var basic = AirPodsGen4(scanResult = scanResult, payload = payload)
var basic = AirPodsGen4(scanResult = scanResult, payload = payload, flags = flags)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -20,6 +20,7 @@ data class AirPodsGen4Anc(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
override val flags: ApplePods.Flags,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -47,9 +48,10 @@ data class AirPodsGen4Anc(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload
payload: ProximityPayload,
flags: ApplePods.Flags
): ApplePods {
var basic = AirPodsGen4Anc(scanResult = scanResult, payload = payload)
var basic = AirPodsGen4Anc(scanResult = scanResult, payload = payload, flags = flags)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -24,6 +24,7 @@ data class AirPodsMax(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
override val flags: ApplePods.Flags,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
) : SingleApplePods, HasEarDetection, HasChargeDetection, HasAppleColor {
@@ -49,9 +50,10 @@ data class AirPodsMax(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload
payload: ProximityPayload,
flags: ApplePods.Flags
): ApplePods {
var basic = AirPodsMax(scanResult = scanResult, payload = payload)
var basic = AirPodsMax(scanResult = scanResult, payload = payload, flags = flags)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -24,6 +24,7 @@ data class AirPodsMaxUsbc(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
override val flags: ApplePods.Flags,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
) : SingleApplePods, HasEarDetection, HasChargeDetection, HasAppleColor {
@@ -49,9 +50,10 @@ data class AirPodsMaxUsbc(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload
payload: ProximityPayload,
flags: ApplePods.Flags
): ApplePods {
var basic = AirPodsMaxUsbc(scanResult = scanResult, payload = payload)
var basic = AirPodsMaxUsbc(scanResult = scanResult, payload = payload, flags = flags)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -23,6 +23,7 @@ data class AirPodsPro(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
override val flags: ApplePods.Flags,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -66,11 +67,13 @@ data class AirPodsPro(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload
payload: ProximityPayload,
flags: ApplePods.Flags
): ApplePods {
var basic = AirPodsPro(
scanResult = scanResult,
payload = payload,
flags = flags,
)
val result = repo.search(basic)
@@ -23,6 +23,7 @@ data class AirPodsPro2(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
override val flags: ApplePods.Flags,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -66,9 +67,10 @@ data class AirPodsPro2(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload
payload: ProximityPayload,
flags: ApplePods.Flags
): ApplePods {
var basic = AirPodsPro2(scanResult = scanResult, payload = payload)
var basic = AirPodsPro2(scanResult = scanResult, payload = payload, flags = flags)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -23,6 +23,7 @@ data class AirPodsPro2Usbc(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
override val flags: ApplePods.Flags,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -66,9 +67,10 @@ data class AirPodsPro2Usbc(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload
payload: ProximityPayload,
flags: ApplePods.Flags
): ApplePods {
var basic = AirPodsPro2Usbc(scanResult = scanResult, payload = payload)
var basic = AirPodsPro2Usbc(scanResult = scanResult, payload = payload, flags = flags)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -20,6 +20,7 @@ data class BeatsFitPro(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
override val flags: ApplePods.Flags,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -47,9 +48,10 @@ data class BeatsFitPro(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload
payload: ProximityPayload,
flags: ApplePods.Flags
): ApplePods {
var basic = BeatsFitPro(scanResult = scanResult, payload = payload)
var basic = BeatsFitPro(scanResult = scanResult, payload = payload, flags = flags)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -20,6 +20,7 @@ data class BeatsFlex(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
override val flags: ApplePods.Flags,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
) : SingleApplePods {
@@ -39,9 +40,10 @@ data class BeatsFlex(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload
payload: ProximityPayload,
flags: ApplePods.Flags
): ApplePods {
var basic = BeatsFlex(scanResult = scanResult, payload = payload)
var basic = BeatsFlex(scanResult = scanResult, payload = payload, flags = flags)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -20,6 +20,7 @@ data class BeatsSolo3(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
override val flags: ApplePods.Flags,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
) : SingleApplePods {
@@ -38,9 +39,10 @@ data class BeatsSolo3(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload
payload: ProximityPayload,
flags: ApplePods.Flags
): ApplePods {
var basic = BeatsSolo3(scanResult = scanResult, payload = payload)
var basic = BeatsSolo3(scanResult = scanResult, payload = payload, flags = flags)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -20,6 +20,7 @@ data class BeatsStudio3(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
override val flags: ApplePods.Flags,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
) : SingleApplePods {
@@ -36,9 +37,10 @@ data class BeatsStudio3(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload
payload: ProximityPayload,
flags: ApplePods.Flags
): ApplePods {
var basic = BeatsStudio3(scanResult = scanResult, payload = payload)
var basic = BeatsStudio3(scanResult = scanResult, payload = payload, flags = flags)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -20,6 +20,7 @@ data class BeatsX(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
override val flags: ApplePods.Flags,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
) : SingleApplePods {
@@ -39,9 +40,10 @@ data class BeatsX(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload
payload: ProximityPayload,
flags: ApplePods.Flags
): ApplePods {
var basic = BeatsX(scanResult = scanResult, payload = payload)
var basic = BeatsX(scanResult = scanResult, payload = payload, flags = flags)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -20,6 +20,7 @@ data class PowerBeats3(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
override val flags: ApplePods.Flags,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
) : SingleApplePods {
@@ -39,9 +40,10 @@ data class PowerBeats3(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload
payload: ProximityPayload,
flags: ApplePods.Flags
): ApplePods {
var basic = PowerBeats3(scanResult = scanResult, payload = payload)
var basic = PowerBeats3(scanResult = scanResult, payload = payload, flags = flags)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -21,6 +21,7 @@ data class PowerBeats4(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
override val flags: ApplePods.Flags,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
) : SingleApplePods, HasStateDetectionAirPods {
@@ -40,9 +41,10 @@ data class PowerBeats4(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload
payload: ProximityPayload,
flags: ApplePods.Flags
): ApplePods {
var basic = PowerBeats4(scanResult = scanResult, payload = payload)
var basic = PowerBeats4(scanResult = scanResult, payload = payload, flags = flags)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -20,6 +20,7 @@ data class PowerBeatsPro(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
override val flags: ApplePods.Flags,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -47,9 +48,10 @@ data class PowerBeatsPro(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload
payload: ProximityPayload,
flags: ApplePods.Flags
): ApplePods {
var basic = PowerBeatsPro(scanResult = scanResult, payload = payload)
var basic = PowerBeatsPro(scanResult = scanResult, payload = payload, flags = flags)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -27,6 +27,7 @@ data class FakeAirPodsGen1(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
override val flags: ApplePods.Flags,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -51,9 +52,10 @@ data class FakeAirPodsGen1(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload
payload: ProximityPayload,
flags: ApplePods.Flags
): ApplePods {
var basic = FakeAirPodsGen1(scanResult = scanResult, payload = payload)
var basic = FakeAirPodsGen1(scanResult = scanResult, payload = payload, flags = flags)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -25,6 +25,7 @@ data class FakeAirPodsGen2(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
override val flags: ApplePods.Flags,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -49,9 +50,10 @@ data class FakeAirPodsGen2(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload
payload: ProximityPayload,
flags: ApplePods.Flags
): ApplePods {
var basic = FakeAirPodsGen2(scanResult = scanResult, payload = payload)
var basic = FakeAirPodsGen2(scanResult = scanResult, payload = payload, flags = flags)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -27,6 +27,7 @@ data class FakeAirPodsGen3(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
override val flags: ApplePods.Flags,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -51,9 +52,10 @@ data class FakeAirPodsGen3(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload
payload: ProximityPayload,
flags: ApplePods.Flags
): ApplePods {
var basic = FakeAirPodsGen3(scanResult = scanResult, payload = payload)
var basic = FakeAirPodsGen3(scanResult = scanResult, payload = payload, flags = flags)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -29,6 +29,7 @@ data class FakeAirPodsPro(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
override val flags: ApplePods.Flags,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -69,9 +70,10 @@ data class FakeAirPodsPro(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload
payload: ProximityPayload,
flags: ApplePods.Flags
): ApplePods {
var basic = FakeAirPodsPro(scanResult = scanResult, payload = payload)
var basic = FakeAirPodsPro(scanResult = scanResult, payload = payload, flags = flags)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -30,6 +30,7 @@ data class FakeAirPodsPro2(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
override val flags: ApplePods.Flags,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -73,9 +74,10 @@ data class FakeAirPodsPro2(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload
payload: ProximityPayload,
flags: ApplePods.Flags
): ApplePods {
var basic = FakeAirPodsPro2(scanResult = scanResult, payload = payload)
var basic = FakeAirPodsPro2(scanResult = scanResult, payload = payload, flags = flags)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -20,6 +20,7 @@ data class UnknownAppleDevice(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
override val flags: ApplePods.Flags,
override val reliability: Float = 0f,
private val rssiAverage: Int? = null,
) : ApplePods {
@@ -39,8 +40,9 @@ data class UnknownAppleDevice(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags,
): ApplePods {
var basic = UnknownAppleDevice(scanResult = scanResult, payload = payload)
var basic = UnknownAppleDevice(scanResult = scanResult, payload = payload, flags = flags)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)