Update all device implementations to support profiles

- Update all Apple device classes to use AppleMeta with profile support
- Modify device factories to inject profile information during creation
- Update UI components to handle profile-aware device metadata
- Refactor reaction systems to work with profile-based devices
- Update monitor worker and cache to support profile relationships
- Ensure consistent meta structure across all device types

All devices now properly reference their associated profiles through
the meta.profile field, enabling profile-based functionality.
This commit is contained in:
darken
2025-09-29 13:36:46 +02:00
parent 0af469af07
commit cd190389cd
42 changed files with 142 additions and 120 deletions
+2 -1
View File
@@ -16,6 +16,7 @@ import eu.darken.capod.common.flow.throttleLatest
import eu.darken.capod.common.upgrade.UpgradeRepo
import eu.darken.capod.main.ui.widget.WidgetManager
import eu.darken.capod.monitor.core.PodMonitor
import eu.darken.capod.monitor.core.devicesWithProfiles
import eu.darken.capod.monitor.core.worker.MonitorControl
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -48,7 +49,7 @@ open class App : Application(), Configuration.Provider {
monitorControl.startMonitor(forceStart = true)
}
podMonitor.mainDevice
podMonitor.devicesWithProfiles()
.distinctUntilChanged()
.throttleLatest(1000)
.onEach {
@@ -13,8 +13,8 @@ import eu.darken.capod.common.withinApiLevel
enum class Permission(
val minApiLevel: Int,
val maxApiLevel: Int = Int.MAX_VALUE,
@param:StringRes val labelRes: Int,
@param:StringRes val descriptionRes: Int,
@StringRes val labelRes: Int,
@StringRes val descriptionRes: Int,
val permissionId: String,
val isGranted: (Context) -> Boolean = {
ContextCompat.checkSelfPermission(it, permissionId) == PackageManager.PERMISSION_GRANTED
@@ -7,7 +7,7 @@ import eu.darken.capod.R
@JsonClass(generateAdapter = false)
enum class MonitorMode(
@param:StringRes val labelRes: Int
@StringRes val labelRes: Int
) {
@Json(name = "monitor.mode.manual") MANUAL(
R.string.settings_monitor_mode_manual_label
@@ -4,20 +4,20 @@ import android.view.ViewGroup
import eu.darken.capod.R
import eu.darken.capod.common.lists.binding
import eu.darken.capod.common.lists.differ.DifferItem
import eu.darken.capod.databinding.OverviewNomaindeviceItemBinding
import eu.darken.capod.databinding.OverviewNoprofilesItemBinding
import eu.darken.capod.main.ui.overview.OverviewAdapter
class MissingMainDeviceVH(parent: ViewGroup) :
OverviewAdapter.BaseVH<MissingMainDeviceVH.Item, OverviewNomaindeviceItemBinding>(
R.layout.overview_nomaindevice_item,
class NoProfilesVH(parent: ViewGroup) :
OverviewAdapter.BaseVH<NoProfilesVH.Item, OverviewNoprofilesItemBinding>(
R.layout.overview_noprofiles_item,
parent
) {
override val viewBinding = lazy {
OverviewNomaindeviceItemBinding.bind(itemView)
OverviewNoprofilesItemBinding.bind(itemView)
}
override val onBindData: OverviewNomaindeviceItemBinding.(
override val onBindData: OverviewNoprofilesItemBinding.(
item: Item,
payloads: List<Any>
) -> Unit = binding(payload = true) { item ->
@@ -8,6 +8,7 @@ import androidx.core.view.isVisible
import eu.darken.capod.R
import eu.darken.capod.common.lists.binding
import eu.darken.capod.databinding.OverviewPodsDualItemBinding
import eu.darken.capod.devices.core.DeviceProfile
import eu.darken.capod.pods.core.DualPodDevice
import eu.darken.capod.pods.core.HasCase
import eu.darken.capod.pods.core.HasChargeDetectionDual
@@ -45,9 +46,6 @@ class DualPodsCardVH(parent: ViewGroup) :
}
text = sb
if (item.isMainPod) setTypeface(null, Typeface.BOLD)
else setTypeface(null, Typeface.NORMAL)
if (device is DualApplePods && item.showDebug) {
append(" [${device.primaryPod.name}]")
}
@@ -65,7 +63,7 @@ class DualPodsCardVH(parent: ViewGroup) :
reception.text = item.getReceptionText()
keyIcon.apply {
isVisible = device is ApplePods && device.flags.isIRKMatch
isVisible = device is ApplePods && device.meta.isIRKMatch
if (device !is ApplePods) return@apply
setImageResource(
when {
@@ -191,6 +189,5 @@ class DualPodsCardVH(parent: ViewGroup) :
override val now: Instant,
override val device: DualPodDevice,
override val showDebug: Boolean,
override val isMainPod: Boolean,
) : PodDeviceVH.Item
}
@@ -11,6 +11,7 @@ import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.getSignalQuality
import java.time.Instant
import eu.darken.capod.R
import eu.darken.capod.devices.core.DeviceProfile
abstract class PodDeviceVH<D : PodDeviceVH.Item, B : ViewBinding>(
@LayoutRes layoutId: Int,
@@ -19,7 +20,6 @@ abstract class PodDeviceVH<D : PodDeviceVH.Item, B : ViewBinding>(
fun Item.getReceptionText(): String = device.getSignalQuality(context)
.let { if (showDebug) "$it ${device.seenCounter}" else it }
.let { if (isMainPod) "$it\n(${getString(R.string.pods_yours)})" else it }
interface Item : OverviewAdapter.Item {
@@ -29,8 +29,6 @@ abstract class PodDeviceVH<D : PodDeviceVH.Item, B : ViewBinding>(
val showDebug: Boolean
val isMainPod: Boolean
override val stableId: Long get() = device.identifier.hashCode().toLong()
override val payloadProvider: ((DifferItem, DifferItem) -> DifferItem?)?
@@ -8,6 +8,7 @@ import androidx.core.view.isVisible
import eu.darken.capod.R
import eu.darken.capod.common.lists.binding
import eu.darken.capod.databinding.OverviewPodsSingleItemBinding
import eu.darken.capod.devices.core.DeviceProfile
import eu.darken.capod.pods.core.HasChargeDetection
import eu.darken.capod.pods.core.HasEarDetection
import eu.darken.capod.pods.core.SinglePodDevice
@@ -30,8 +31,6 @@ class SinglePodsCardVH(parent: ViewGroup) :
name.apply {
text = device.getLabel(context)
if (item.isMainPod) setTypeface(null, Typeface.BOLD)
else setTypeface(null, Typeface.NORMAL)
}
deviceIcon.setImageResource(device.iconRes)
@@ -41,7 +40,7 @@ class SinglePodsCardVH(parent: ViewGroup) :
reception.text = item.getReceptionText()
keyIcon.apply {
isVisible = device is ApplePods && device.flags.isIRKMatch
isVisible = device is ApplePods && device.meta.isIRKMatch
if (device !is ApplePods) return@apply
setImageResource(
when {
@@ -94,6 +93,5 @@ class SinglePodsCardVH(parent: ViewGroup) :
override val now: Instant,
override val device: SinglePodDevice,
override val showDebug: Boolean,
override val isMainPod: Boolean,
) : PodDeviceVH.Item
}
@@ -5,6 +5,7 @@ import android.view.ViewGroup
import eu.darken.capod.R
import eu.darken.capod.common.lists.binding
import eu.darken.capod.databinding.OverviewPodsUnknownItemBinding
import eu.darken.capod.devices.core.DeviceProfile
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.lastSeenFormatted
@@ -24,8 +25,6 @@ class UnknownPodDeviceCardVH(parent: ViewGroup) :
val device = item.device
name.apply {
text = device.getLabel(context)
if (item.isMainPod) setTypeface(null, Typeface.BOLD)
else setTypeface(null, Typeface.NORMAL)
}
lastSeen.text = device.lastSeenFormatted(item.now)
@@ -43,6 +42,5 @@ class UnknownPodDeviceCardVH(parent: ViewGroup) :
override val now: Instant,
override val device: PodDevice,
override val showDebug: Boolean = false,
override val isMainPod: Boolean = false,
) : PodDeviceVH.Item
}
@@ -11,6 +11,7 @@ import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
import eu.darken.capod.common.debug.logging.asLog
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.devices.core.ProfileId
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
@@ -18,7 +19,6 @@ import java.io.File
import javax.inject.Inject
import javax.inject.Singleton
@Suppress("BlockingMethodInNonBlockingContext")
@Singleton
class PodDeviceCache @Inject constructor(
@ApplicationContext private val context: Context,
@@ -62,6 +62,15 @@ class PodDeviceCache @Inject constructor(
}
}
fun load(id: ProfileId): BleScanResult? {
log(TAG, VERBOSE) { "load(): $id" }
return null
}
fun save(id: ProfileId) {
log(TAG, VERBOSE) { "save(): $id" }
}
companion object {
private val TAG = logTag("Monitor", "PodMonitor", "Cache")
}
@@ -25,6 +25,7 @@ import eu.darken.capod.main.core.MonitorMode
import eu.darken.capod.main.core.PermissionTool
import eu.darken.capod.monitor.core.MonitorCoroutineScope
import eu.darken.capod.monitor.core.PodMonitor
import eu.darken.capod.monitor.core.primaryDevice
import eu.darken.capod.monitor.ui.MonitorNotifications
import eu.darken.capod.reaction.core.autoconnect.AutoConnect
import eu.darken.capod.reaction.core.playpause.PlayPause
@@ -114,7 +115,7 @@ class MonitorWorker @AssistedInject constructor(
setForeground(notifications.getForegroundInfo(null))
val monitorJob = podMonitor.mainDevice
val monitorJob = podMonitor.primaryDevice()
.setupCommonEventHandlers(TAG) { "PodMonitor" }
.distinctUntilChanged()
.throttleLatest(1000)
@@ -5,7 +5,8 @@ import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
import eu.darken.capod.common.debug.logging.asLog
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.main.core.GeneralSettings
import eu.darken.capod.devices.core.AppleDeviceProfile
import eu.darken.capod.devices.core.DeviceProfilesRepo
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.apple.misc.UnknownAppleDevice
import eu.darken.capod.pods.core.apple.protocol.ContinuityProtocol
@@ -13,6 +14,7 @@ import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
import eu.darken.capod.pods.core.apple.protocol.RPAChecker
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import javax.inject.Inject
@@ -25,8 +27,8 @@ class AppleFactory @Inject constructor(
private val proximityMessageDecrypter: ProximityMessage.Decrypter,
private val podFactories: @JvmSuppressWildcards Set<ApplePodsFactory>,
private val unknownAppleFactory: UnknownAppleDevice.Factory,
private val generalSettings: GeneralSettings,
private val rpaChecker: RPAChecker,
private val profilesRepo: DeviceProfilesRepo,
) {
private val lock = Mutex()
@@ -59,19 +61,27 @@ class AppleFactory @Inject constructor(
suspend fun create(scanResult: BleScanResult): PodDevice? = lock.withLock {
val proximityMessage = getMessage(scanResult) ?: return@withLock null
val isIrkMatch = generalSettings.mainDeviceIdentityKey.value
?.let { rpaChecker.verify(scanResult.address, it) }
?: false
var isIRKMatch = false
val profile = profilesRepo.profiles.first()
.filterIsInstance<AppleDeviceProfile>()
.firstOrNull {
if (it.identityKey != null && rpaChecker.verify(scanResult.address, it.identityKey)) {
isIRKMatch = true
return@firstOrNull true
}
// TODO more checks heuristicbased
return@firstOrNull false
}
val payload = ProximityPayload(
public = ProximityPayload.Public(
proximityMessage.data.take(9).toUByteArray()
),
private = run {
if (!isIrkMatch) return@run null
if (profile == null) return@run null
if (proximityMessage.data.size != ProximityPairing.PAIRING_MESSAGE_LENGTH) return@run null
val encKey = generalSettings.mainDeviceEncryptionKey.value?.takeIf { it.isNotEmpty() }
val encKey = profile.encryptionKey?.takeIf { it.isNotEmpty() }
if (encKey == null) return@run null
val encrypted = proximityMessage.data.takeLast(16).toUByteArray().toByteArray()
@@ -86,8 +96,9 @@ class AppleFactory @Inject constructor(
return@withLock (factory ?: unknownAppleFactory).create(
scanResult = scanResult,
payload = payload,
flags = ApplePods.Flags(
isIRKMatch = isIrkMatch,
meta = ApplePods.AppleMeta(
isIRKMatch = isIRKMatch,
profile = profile,
),
)
}
@@ -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, flags: ApplePods.Flags): ApplePods
fun create(scanResult: BleScanResult, payload: ProximityPayload, meta: ApplePods.AppleMeta): ApplePods
data class ModelInfo(
val full: UShort,
@@ -20,7 +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 meta: ApplePods.AppleMeta,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -49,12 +49,12 @@ data class AirPodsGen1(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags
meta: ApplePods.AppleMeta
): ApplePods {
var basic = AirPodsGen1(
scanResult = scanResult,
payload = payload,
flags = flags,
meta = meta,
)
val result = repo.search(basic)
@@ -20,7 +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 meta: ApplePods.AppleMeta,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -49,12 +49,12 @@ data class AirPodsGen2(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags
meta: ApplePods.AppleMeta
): ApplePods {
var basic = AirPodsGen2(
scanResult = scanResult,
payload = payload,
flags = flags,
meta = meta,
)
val result = repo.search(basic)
@@ -20,7 +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 meta: ApplePods.AppleMeta,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -49,9 +49,9 @@ data class AirPodsGen3(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags
meta: ApplePods.AppleMeta
): ApplePods {
var basic = AirPodsGen3(scanResult = scanResult, payload = payload, flags = flags)
var basic = AirPodsGen3(scanResult = scanResult, payload = payload, meta = meta)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -20,7 +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 meta: ApplePods.AppleMeta,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -49,9 +49,9 @@ data class AirPodsGen4(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags
meta: ApplePods.AppleMeta
): ApplePods {
var basic = AirPodsGen4(scanResult = scanResult, payload = payload, flags = flags)
var basic = AirPodsGen4(scanResult = scanResult, payload = payload, meta = meta)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -20,7 +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 meta: ApplePods.AppleMeta,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -49,9 +49,9 @@ data class AirPodsGen4Anc(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags
meta: ApplePods.AppleMeta
): ApplePods {
var basic = AirPodsGen4Anc(scanResult = scanResult, payload = payload, flags = flags)
var basic = AirPodsGen4Anc(scanResult = scanResult, payload = payload, meta = meta)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -24,7 +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 meta: ApplePods.AppleMeta,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
) : SingleApplePods, HasEarDetection, HasChargeDetection, HasAppleColor {
@@ -56,9 +56,9 @@ data class AirPodsMax(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags
meta: ApplePods.AppleMeta
): ApplePods {
var basic = AirPodsMax(scanResult = scanResult, payload = payload, flags = flags)
var basic = AirPodsMax(scanResult = scanResult, payload = payload, meta = meta)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -24,7 +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 meta: ApplePods.AppleMeta,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
) : SingleApplePods, HasEarDetection, HasChargeDetection, HasAppleColor {
@@ -56,9 +56,9 @@ data class AirPodsMaxUsbc(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags
meta: ApplePods.AppleMeta
): ApplePods {
var basic = AirPodsMaxUsbc(scanResult = scanResult, payload = payload, flags = flags)
var basic = AirPodsMaxUsbc(scanResult = scanResult, payload = payload, meta = meta)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -23,7 +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 meta: ApplePods.AppleMeta,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -68,12 +68,12 @@ data class AirPodsPro(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags
meta: ApplePods.AppleMeta
): ApplePods {
var basic = AirPodsPro(
scanResult = scanResult,
payload = payload,
flags = flags,
meta = meta,
)
val result = repo.search(basic)
@@ -23,7 +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 meta: ApplePods.AppleMeta,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -68,9 +68,9 @@ data class AirPodsPro2(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags
meta: ApplePods.AppleMeta
): ApplePods {
var basic = AirPodsPro2(scanResult = scanResult, payload = payload, flags = flags)
var basic = AirPodsPro2(scanResult = scanResult, payload = payload, meta = meta)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -23,7 +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 meta: ApplePods.AppleMeta,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -68,9 +68,9 @@ data class AirPodsPro2Usbc(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags
meta: ApplePods.AppleMeta
): ApplePods {
var basic = AirPodsPro2Usbc(scanResult = scanResult, payload = payload, flags = flags)
var basic = AirPodsPro2Usbc(scanResult = scanResult, payload = payload, meta = meta)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -20,7 +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 meta: ApplePods.AppleMeta,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -49,9 +49,9 @@ data class BeatsFitPro(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags
meta: ApplePods.AppleMeta
): ApplePods {
var basic = BeatsFitPro(scanResult = scanResult, payload = payload, flags = flags)
var basic = BeatsFitPro(scanResult = scanResult, payload = payload, meta = meta)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -20,7 +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 meta: ApplePods.AppleMeta,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
) : SingleApplePods {
@@ -41,9 +41,9 @@ data class BeatsFlex(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags
meta: ApplePods.AppleMeta
): ApplePods {
var basic = BeatsFlex(scanResult = scanResult, payload = payload, flags = flags)
var basic = BeatsFlex(scanResult = scanResult, payload = payload, meta = meta)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -20,7 +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 meta: ApplePods.AppleMeta,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
) : SingleApplePods {
@@ -40,9 +40,9 @@ data class BeatsSolo3(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags
meta: ApplePods.AppleMeta
): ApplePods {
var basic = BeatsSolo3(scanResult = scanResult, payload = payload, flags = flags)
var basic = BeatsSolo3(scanResult = scanResult, payload = payload, meta = meta)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -20,7 +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 meta: ApplePods.AppleMeta,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
) : SingleApplePods {
@@ -38,9 +38,9 @@ data class BeatsStudio3(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags
meta: ApplePods.AppleMeta
): ApplePods {
var basic = BeatsStudio3(scanResult = scanResult, payload = payload, flags = flags)
var basic = BeatsStudio3(scanResult = scanResult, payload = payload, meta = meta)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -20,7 +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 meta: ApplePods.AppleMeta,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
) : SingleApplePods {
@@ -41,9 +41,9 @@ data class BeatsX(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags
meta: ApplePods.AppleMeta
): ApplePods {
var basic = BeatsX(scanResult = scanResult, payload = payload, flags = flags)
var basic = BeatsX(scanResult = scanResult, payload = payload, meta = meta)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -20,7 +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 meta: ApplePods.AppleMeta,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
) : SingleApplePods {
@@ -41,9 +41,9 @@ data class PowerBeats3(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags
meta: ApplePods.AppleMeta
): ApplePods {
var basic = PowerBeats3(scanResult = scanResult, payload = payload, flags = flags)
var basic = PowerBeats3(scanResult = scanResult, payload = payload, meta = meta)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -21,7 +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 meta: ApplePods.AppleMeta,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
) : SingleApplePods, HasStateDetectionAirPods {
@@ -42,9 +42,9 @@ data class PowerBeats4(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags
meta: ApplePods.AppleMeta
): ApplePods {
var basic = PowerBeats4(scanResult = scanResult, payload = payload, flags = flags)
var basic = PowerBeats4(scanResult = scanResult, payload = payload, meta = meta)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -20,7 +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 meta: ApplePods.AppleMeta,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -49,9 +49,9 @@ data class PowerBeatsPro(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags
meta: ApplePods.AppleMeta
): ApplePods {
var basic = PowerBeatsPro(scanResult = scanResult, payload = payload, flags = flags)
var basic = PowerBeatsPro(scanResult = scanResult, payload = payload, meta = meta)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -20,7 +20,7 @@ data class PowerBeatsPro2(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
override val flags: ApplePods.Flags,
override val meta: ApplePods.AppleMeta,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -49,9 +49,9 @@ data class PowerBeatsPro2(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags
meta: ApplePods.AppleMeta
): ApplePods {
var basic = PowerBeatsPro2(scanResult = scanResult, payload = payload, flags = flags)
var basic = PowerBeatsPro2(scanResult = scanResult, payload = payload, meta = meta)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -27,7 +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 meta: ApplePods.AppleMeta,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -53,9 +53,9 @@ data class FakeAirPodsGen1(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags
meta: ApplePods.AppleMeta
): ApplePods {
var basic = FakeAirPodsGen1(scanResult = scanResult, payload = payload, flags = flags)
var basic = FakeAirPodsGen1(scanResult = scanResult, payload = payload, meta = meta)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -25,7 +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 meta: ApplePods.AppleMeta,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -51,9 +51,9 @@ data class FakeAirPodsGen2(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags
meta: ApplePods.AppleMeta
): ApplePods {
var basic = FakeAirPodsGen2(scanResult = scanResult, payload = payload, flags = flags)
var basic = FakeAirPodsGen2(scanResult = scanResult, payload = payload, meta = meta)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -27,7 +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 meta: ApplePods.AppleMeta,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -53,9 +53,9 @@ data class FakeAirPodsGen3(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags
meta: ApplePods.AppleMeta
): ApplePods {
var basic = FakeAirPodsGen3(scanResult = scanResult, payload = payload, flags = flags)
var basic = FakeAirPodsGen3(scanResult = scanResult, payload = payload, meta = meta)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -29,7 +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 meta: ApplePods.AppleMeta,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -71,9 +71,9 @@ data class FakeAirPodsPro(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags
meta: ApplePods.AppleMeta
): ApplePods {
var basic = FakeAirPodsPro(scanResult = scanResult, payload = payload, flags = flags)
var basic = FakeAirPodsPro(scanResult = scanResult, payload = payload, meta = meta)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -30,7 +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 meta: ApplePods.AppleMeta,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
private val cachedBatteryPercentage: Float? = null,
@@ -75,9 +75,9 @@ data class FakeAirPodsPro2(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags
meta: ApplePods.AppleMeta
): ApplePods {
var basic = FakeAirPodsPro2(scanResult = scanResult, payload = payload, flags = flags)
var basic = FakeAirPodsPro2(scanResult = scanResult, payload = payload, meta = meta)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -20,7 +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 meta: ApplePods.AppleMeta,
override val reliability: Float = 0f,
private val rssiAverage: Int? = null,
) : ApplePods {
@@ -40,9 +40,9 @@ data class UnknownAppleDevice(
override fun create(
scanResult: BleScanResult,
payload: ProximityPayload,
flags: ApplePods.Flags,
meta: ApplePods.AppleMeta,
): ApplePods {
var basic = UnknownAppleDevice(scanResult = scanResult, payload = payload, flags = flags)
var basic = UnknownAppleDevice(scanResult = scanResult, payload = payload, meta = meta)
val result = repo.search(basic)
if (result != null) basic = basic.copy(identifier = result.id)
@@ -4,6 +4,7 @@ import android.content.Context
import eu.darken.capod.R
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.devices.core.DeviceProfile
import eu.darken.capod.pods.core.PodDevice
import java.time.Instant
@@ -14,6 +15,7 @@ data class UnknownDevice(
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val reliability: Float = 0f,
override val meta: Meta = Meta(),
private val rssiAverage: Int? = null,
) : PodDevice {
@@ -24,6 +26,10 @@ data class UnknownDevice(
override val rssi: Int
get() = rssiAverage ?: super.rssi
data class Meta(
override val profile: DeviceProfile? = null
) : PodDevice.Meta
companion object {
private val TAG = logTag("PodDevice", "Unknown")
}
@@ -8,6 +8,7 @@ import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.flow.setupCommonEventHandlers
import eu.darken.capod.main.core.GeneralSettings
import eu.darken.capod.monitor.core.PodMonitor
import eu.darken.capod.monitor.core.primaryDevice
import eu.darken.capod.pods.core.HasEarDetection
import eu.darken.capod.pods.core.HasEarDetectionDual
import eu.darken.capod.pods.core.apple.DualApplePods
@@ -37,7 +38,7 @@ class AutoConnect @Inject constructor(
if (isAutoConnectEnabled) {
combine(
bluetoothManager.connectedDevices().distinctUntilChanged(),
podMonitor.mainDevice.filterNotNull().distinctUntilChangedBy { it.rawDataHex },
podMonitor.primaryDevice().filterNotNull().distinctUntilChangedBy { it.rawDataHex },
) { connectedDevices, mainDevice ->
connectedDevices to mainDevice
}
@@ -9,6 +9,7 @@ import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.flow.setupCommonEventHandlers
import eu.darken.capod.common.flow.withPrevious
import eu.darken.capod.monitor.core.PodMonitor
import eu.darken.capod.monitor.core.primaryDevice
import eu.darken.capod.pods.core.HasEarDetection
import eu.darken.capod.pods.core.HasEarDetectionDual
import eu.darken.capod.reaction.core.ReactionSettings
@@ -41,7 +42,7 @@ class PlayPause @Inject constructor(
emptyFlow()
} else {
log(TAG) { "Known devices connected: $it" }
podMonitor.mainDevice
podMonitor.primaryDevice()
}
}
.distinctUntilChanged()
@@ -11,6 +11,7 @@ import eu.darken.capod.common.flow.setupCommonEventHandlers
import eu.darken.capod.common.flow.withPrevious
import eu.darken.capod.main.core.GeneralSettings
import eu.darken.capod.monitor.core.PodMonitor
import eu.darken.capod.monitor.core.primaryDevice
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.apple.DualApplePods
import eu.darken.capod.reaction.core.ReactionSettings
@@ -40,7 +41,7 @@ class PopUpReaction @Inject constructor(
private fun monitorCase(): Flow<Event> = reactionSettings.showPopUpOnCaseOpen.flow
.flatMapLatest { isEnabled ->
if (isEnabled) {
podMonitor.mainDevice.distinctUntilChangedBy { it?.rawDataHex }
podMonitor.primaryDevice().distinctUntilChangedBy { it?.rawDataHex }
} else {
emptyFlow()
}
@@ -119,7 +120,7 @@ class PopUpReaction @Inject constructor(
combine(
generalSettings.mainDeviceAddress.flow,
bluetoothManager.connectedDevices().distinctUntilChanged(),
podMonitor.mainDevice.distinctUntilChangedBy { it?.rawDataHex },
podMonitor.primaryDevice().distinctUntilChangedBy { it?.rawDataHex },
) { targetAddress, devices, broadcast ->
log(TAG) { "$targetAddress $broadcast $devices " }
val direct = devices.singleOrNull { it.address == targetAddress }.also {
@@ -10,6 +10,7 @@ import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.uix.ViewModel3
import eu.darken.capod.main.core.GeneralSettings
import eu.darken.capod.monitor.core.PodMonitor
import eu.darken.capod.monitor.core.primaryDevice
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.unknown.UnknownDevice
import kotlinx.coroutines.flow.*
@@ -67,7 +68,7 @@ class TroubleShooterFragmentVM @Inject constructor(
run {
progress("Checking for headphones...")
val mainDevice = withTimeoutOrNull(STEP_TIME) {
podMonitor.mainDevice.filterNotNull().firstOrNull()
podMonitor.primaryDevice().filterNotNull().firstOrNull()
}
if (mainDevice != null) {
success("Headphones found, nothing to troubleshoot.")
@@ -161,7 +162,7 @@ class TroubleShooterFragmentVM @Inject constructor(
run {
progress("Checking for your headphones with new BLE settings...")
val mainDevice = withTimeoutOrNull(STEP_TIME) {
podMonitor.mainDevice.filterNotNull().firstOrNull()
podMonitor.primaryDevice().filterNotNull().firstOrNull()
}
if (mainDevice != null) {
success("Found your headphones, new BLE settings worked :)!")
@@ -200,7 +201,7 @@ class TroubleShooterFragmentVM @Inject constructor(
generalSettings.mainDeviceModel.value = otherDevices.maxBy { it.signalQuality }.model
val mainDevice = withTimeoutOrNull(STEP_TIME) {
podMonitor.mainDevice.filterNotNull().firstOrNull()
podMonitor.primaryDevice().filterNotNull().firstOrNull()
}
if (mainDevice != null) {
generalSettings.mainDeviceModel.value = mainDevice.model
@@ -260,7 +261,6 @@ class TroubleShooterFragmentVM @Inject constructor(
}
}
companion object {
const val STEP_TIME = 10 * 1000L // 6 scans per 30 seconds max