diff --git a/app/src/main/java/eu/darken/capod/common/compose/preview/MockPodDataProvider.kt b/app/src/main/java/eu/darken/capod/common/compose/preview/MockPodDataProvider.kt index 6ed410c7..591d4413 100644 --- a/app/src/main/java/eu/darken/capod/common/compose/preview/MockPodDataProvider.kt +++ b/app/src/main/java/eu/darken/capod/common/compose/preview/MockPodDataProvider.kt @@ -4,20 +4,20 @@ import android.content.Context import eu.darken.capod.R import eu.darken.capod.common.bluetooth.BleScanResult import eu.darken.capod.common.upgrade.UpgradeRepo -import eu.darken.capod.monitor.core.MonitoredDevice -import eu.darken.capod.pods.core.DualPodDevice +import eu.darken.capod.monitor.core.PodDevice +import eu.darken.capod.pods.core.BlePodSnapshot +import eu.darken.capod.pods.core.DualBlePodSnapshot import eu.darken.capod.pods.core.HasCase import eu.darken.capod.pods.core.HasChargeDetection import eu.darken.capod.pods.core.HasChargeDetectionDual import eu.darken.capod.pods.core.HasDualMicrophone import eu.darken.capod.pods.core.HasEarDetection import eu.darken.capod.pods.core.HasEarDetectionDual -import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.PodModel -import eu.darken.capod.pods.core.SinglePodDevice +import eu.darken.capod.pods.core.SingleBlePodSnapshot import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.protocol.ProximityPayload -import eu.darken.capod.pods.core.unknown.UnknownDevice +import eu.darken.capod.pods.core.unknown.UnknownSnapshotBle import eu.darken.capod.profiles.core.AppleDeviceProfile import eu.darken.capod.profiles.core.DeviceProfile import java.time.Instant @@ -39,7 +39,7 @@ object MockPodDataProvider { // --- Dual pod scenarios --- - fun airPodsProFullCharge(): DualPodDevice = MockDualPodDevice( + fun airPodsProFullCharge(): DualBlePodSnapshot = MockDualBlePodSnapshot( _model = PodModel.AIRPODS_PRO2, _label = "Work AirPods", batteryLeftPodPercent = 1.0f, @@ -50,7 +50,7 @@ object MockPodDataProvider { _caseIcon = R.drawable.device_airpods_pro2_case, ) - fun airPodsProMixed(): DualPodDevice = MockDualPodDevice( + fun airPodsProMixed(): DualBlePodSnapshot = MockDualBlePodSnapshot( _model = PodModel.AIRPODS_PRO2, _label = "My AirPods Pro", batteryLeftPodPercent = 0.80f, @@ -62,7 +62,7 @@ object MockPodDataProvider { _caseIcon = R.drawable.device_airpods_pro2_case, ) - fun airPodsProLowBattery(): DualPodDevice = MockDualPodDevice( + fun airPodsProLowBattery(): DualBlePodSnapshot = MockDualBlePodSnapshot( _model = PodModel.AIRPODS_PRO2, _label = "My AirPods Pro", batteryLeftPodPercent = 0.10f, @@ -75,7 +75,7 @@ object MockPodDataProvider { _caseIcon = R.drawable.device_airpods_pro2_case, ) - fun airPodsProInCase(): DualPodDevice = MockDualPodDevice( + fun airPodsProInCase(): DualBlePodSnapshot = MockDualBlePodSnapshot( _model = PodModel.AIRPODS_PRO2, _label = "My AirPods Pro", batteryLeftPodPercent = null, @@ -86,7 +86,7 @@ object MockPodDataProvider { _caseIcon = R.drawable.device_airpods_pro2_case, ) - fun airPodsGen1Wearing(): DualPodDevice = MockDualPodDevice( + fun airPodsGen1Wearing(): DualBlePodSnapshot = MockDualBlePodSnapshot( _model = PodModel.AIRPODS_GEN1, _label = "Old AirPods", batteryLeftPodPercent = 0.70f, @@ -100,7 +100,7 @@ object MockPodDataProvider { _caseIcon = R.drawable.device_airpods_gen1_case, ) - fun powerBeatsPro(): DualPodDevice = MockDualPodDeviceNoCase( + fun powerBeatsPro(): DualBlePodSnapshot = MockDualBlePodSnapshotNoCase( _model = PodModel.POWERBEATS_PRO, _label = "PowerBeats Pro", batteryLeftPodPercent = 0.55f, @@ -111,14 +111,14 @@ object MockPodDataProvider { // --- Single pod scenarios --- - fun airPodsMax(): SinglePodDevice = MockSinglePodDevice( + fun airPodsMax(): SingleBlePodSnapshot = MockSingleBlePodSnapshot( _model = PodModel.AIRPODS_MAX, _label = "AirPods Max", batteryHeadsetPercent = 0.85f, _isBeingWorn = true, ) - fun airPodsMaxCharging(): SinglePodDevice = MockSingleApplePodDevice( + fun airPodsMaxCharging(): SingleBlePodSnapshot = MockSingleAppleBlePodSnapshot( _model = PodModel.AIRPODS_MAX, _label = "AirPods Max", batteryHeadsetPercent = 0.40f, @@ -127,7 +127,7 @@ object MockPodDataProvider { _hasPrivatePayload = true, ) - fun beatsSolo3(): SinglePodDevice = MockSinglePodDevice( + fun beatsSolo3(): SingleBlePodSnapshot = MockSingleBlePodSnapshot( _model = PodModel.BEATS_SOLO_3, _label = "Beats Solo 3", batteryHeadsetPercent = 0.70f, @@ -135,7 +135,7 @@ object MockPodDataProvider { // --- Unknown device --- - fun unknownDevice(): PodDevice = UnknownDevice( + fun unknownDevice(): BlePodSnapshot = UnknownSnapshotBle( scanResult = dummyScanResult(rssi = -70), ) @@ -146,24 +146,24 @@ object MockPodDataProvider { model = model, ) - // --- MonitoredDevice wrappers --- + // --- PodDevice wrappers --- - fun dualPodMonitored(): MonitoredDevice = MonitoredDevice( + fun dualPodMonitored(): PodDevice = PodDevice( ble = airPodsProFullCharge(), aap = null, ) - fun dualPodMonitoredMixed(): MonitoredDevice = MonitoredDevice( + fun dualPodMonitoredMixed(): PodDevice = PodDevice( ble = airPodsProMixed(), aap = null, ) - fun singlePodMonitored(): MonitoredDevice = MonitoredDevice( + fun singlePodMonitored(): PodDevice = PodDevice( ble = airPodsMax(), aap = null, ) - fun unknownMonitored(): MonitoredDevice = MonitoredDevice( + fun unknownMonitored(): PodDevice = PodDevice( ble = unknownDevice(), aap = null, ) @@ -188,7 +188,7 @@ private data class MockUpgradeInfo( override val error: Throwable? = null, ) : UpgradeRepo.Info -private class MockDualPodDevice( +private class MockDualBlePodSnapshot( private val _model: PodModel, private val _label: String, override val batteryLeftPodPercent: Float?, @@ -205,8 +205,8 @@ private class MockDualPodDevice( override val rightPodIcon: Int = R.drawable.device_airpods_gen1_right, private val _caseIcon: Int = R.drawable.device_airpods_gen1_case, rssi: Int = -50, -) : DualPodDevice, HasCase, HasChargeDetectionDual, HasEarDetectionDual, HasDualMicrophone { - override val identifier: PodDevice.Id = PodDevice.Id() +) : DualBlePodSnapshot, HasCase, HasChargeDetectionDual, HasEarDetectionDual, HasDualMicrophone { + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id() override val model: PodModel = _model override val seenLastAt: Instant = MOCK_NOW override val seenFirstAt: Instant = MOCK_NOW @@ -215,7 +215,7 @@ private class MockDualPodDevice( override val reliability: Float = 1.0f override val signalQuality: Float = 0.75f override val iconRes: Int = _model.iconRes - override val meta: PodDevice.Meta = object : PodDevice.Meta { + override val meta: BlePodSnapshot.Meta = object : BlePodSnapshot.Meta { override val profile: DeviceProfile = AppleDeviceProfile(label = _label, model = _model) } @@ -239,7 +239,7 @@ private class MockDualPodDevice( override val isRightPodMicrophone: Boolean = _isRightPodMicrophone } -private class MockDualPodDeviceNoCase( +private class MockDualBlePodSnapshotNoCase( private val _model: PodModel, private val _label: String, override val batteryLeftPodPercent: Float?, @@ -247,8 +247,8 @@ private class MockDualPodDeviceNoCase( override val leftPodIcon: Int = R.drawable.device_airpods_gen1_left, override val rightPodIcon: Int = R.drawable.device_airpods_gen1_right, rssi: Int = -50, -) : DualPodDevice, HasChargeDetectionDual, HasEarDetectionDual { - override val identifier: PodDevice.Id = PodDevice.Id() +) : DualBlePodSnapshot, HasChargeDetectionDual, HasEarDetectionDual { + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id() override val model: PodModel = _model override val seenLastAt: Instant = MOCK_NOW override val seenFirstAt: Instant = MOCK_NOW @@ -257,7 +257,7 @@ private class MockDualPodDeviceNoCase( override val reliability: Float = 1.0f override val signalQuality: Float = 0.70f override val iconRes: Int = _model.iconRes - override val meta: PodDevice.Meta = object : PodDevice.Meta { + override val meta: BlePodSnapshot.Meta = object : BlePodSnapshot.Meta { override val profile: DeviceProfile = AppleDeviceProfile(label = _label, model = _model) } @@ -272,15 +272,15 @@ private class MockDualPodDeviceNoCase( override val isRightPodInEar: Boolean = false } -private class MockSinglePodDevice( +private class MockSingleBlePodSnapshot( private val _model: PodModel, private val _label: String, override val batteryHeadsetPercent: Float?, private val _isHeadsetBeingCharged: Boolean = false, private val _isBeingWorn: Boolean = false, rssi: Int = -50, -) : SinglePodDevice, HasChargeDetection, HasEarDetection { - override val identifier: PodDevice.Id = PodDevice.Id() +) : SingleBlePodSnapshot, HasChargeDetection, HasEarDetection { + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id() override val model: PodModel = _model override val seenLastAt: Instant = MOCK_NOW override val seenFirstAt: Instant = MOCK_NOW @@ -289,7 +289,7 @@ private class MockSinglePodDevice( override val reliability: Float = 1.0f override val signalQuality: Float = 0.80f override val iconRes: Int = _model.iconRes - override val meta: PodDevice.Meta = object : PodDevice.Meta { + override val meta: BlePodSnapshot.Meta = object : BlePodSnapshot.Meta { override val profile: DeviceProfile = AppleDeviceProfile(label = _label, model = _model) } @@ -303,7 +303,7 @@ private class MockSinglePodDevice( } @Suppress("PropertyName") -private class MockSingleApplePodDevice( +private class MockSingleAppleBlePodSnapshot( private val _model: PodModel, private val _label: String, override val batteryHeadsetPercent: Float?, @@ -312,8 +312,8 @@ private class MockSingleApplePodDevice( private val _isIRKMatch: Boolean = false, private val _hasPrivatePayload: Boolean = false, rssi: Int = -50, -) : SinglePodDevice, ApplePods, HasChargeDetection, HasEarDetection { - override val identifier: PodDevice.Id = PodDevice.Id() +) : SingleBlePodSnapshot, ApplePods, HasChargeDetection, HasEarDetection { + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id() override val model: PodModel = _model override val seenLastAt: Instant = MOCK_NOW override val seenFirstAt: Instant = MOCK_NOW diff --git a/app/src/main/java/eu/darken/capod/main/ui/overview/OverviewScreen.kt b/app/src/main/java/eu/darken/capod/main/ui/overview/OverviewScreen.kt index 1687112f..f29747eb 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/overview/OverviewScreen.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/overview/OverviewScreen.kt @@ -53,7 +53,7 @@ import eu.darken.capod.main.ui.overview.cards.PermissionCard import eu.darken.capod.main.ui.overview.cards.SinglePodsCard import eu.darken.capod.main.ui.overview.cards.UnknownPodDeviceCard import eu.darken.capod.main.ui.overview.cards.UnmatchedDevicesCard -import eu.darken.capod.monitor.core.MonitoredDevice +import eu.darken.capod.monitor.core.PodDevice import eu.darken.capod.pods.core.PodModel import java.time.Instant @@ -257,7 +257,7 @@ fun OverviewScreen( } @Composable -private fun PodDeviceCard(device: MonitoredDevice, showDebug: Boolean, now: Instant) { +private fun PodDeviceCard(device: PodDevice, showDebug: Boolean, now: Instant) { when { device.hasDualPods -> DualPodsCard(device = device, showDebug = showDebug, now = now) device.model != PodModel.UNKNOWN -> SinglePodsCard(device = device, showDebug = showDebug, now = now) diff --git a/app/src/main/java/eu/darken/capod/main/ui/overview/OverviewViewModel.kt b/app/src/main/java/eu/darken/capod/main/ui/overview/OverviewViewModel.kt index 67a4dd8e..1061dbf0 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/overview/OverviewViewModel.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/overview/OverviewViewModel.kt @@ -3,12 +3,12 @@ package eu.darken.capod.main.ui.overview import dagger.hilt.android.lifecycle.HiltViewModel import eu.darken.capod.common.bluetooth.BluetoothManager2 import eu.darken.capod.common.coroutine.DispatcherProvider +import eu.darken.capod.common.datastore.valueBlocking import eu.darken.capod.common.debug.DebugSettings import eu.darken.capod.common.debug.logging.log import eu.darken.capod.common.debug.logging.logTag import eu.darken.capod.common.flow.SingleEventFlow import eu.darken.capod.common.flow.combine - import eu.darken.capod.common.flow.throttleLatest import eu.darken.capod.common.navigation.Nav import eu.darken.capod.common.permissions.Permission @@ -18,11 +18,10 @@ import eu.darken.capod.main.core.GeneralSettings import eu.darken.capod.main.core.MonitorMode import eu.darken.capod.main.core.PermissionTool import eu.darken.capod.monitor.core.DeviceMonitor -import eu.darken.capod.monitor.core.MonitoredDevice +import eu.darken.capod.monitor.core.PodDevice import eu.darken.capod.monitor.core.worker.MonitorControl import eu.darken.capod.profiles.core.DeviceProfile import eu.darken.capod.profiles.core.DeviceProfilesRepo -import java.time.Instant import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.catch @@ -33,8 +32,8 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.isActive import kotlinx.coroutines.withTimeoutOrNull +import java.time.Instant import javax.inject.Inject -import eu.darken.capod.common.datastore.valueBlocking @HiltViewModel class OverviewViewModel @Inject constructor( @@ -71,6 +70,7 @@ class OverviewViewModel @Inject constructor( false } } + MonitorMode.ALWAYS -> true } if (shouldStart) { @@ -122,7 +122,7 @@ class OverviewViewModel @Inject constructor( data class State( val now: Instant, val permissions: Set, - val devices: List, + val devices: List, val isDebugMode: Boolean, val isBluetoothEnabled: Boolean, val profiles: List, @@ -130,8 +130,8 @@ class OverviewViewModel @Inject constructor( val showUnmatchedDevices: Boolean, ) { val isScanBlocked: Boolean get() = permissions.any { it.isScanBlocking } - val profiledDevices: List get() = devices.filter { it.meta?.profile != null } - val unmatchedDevices: List get() = devices.filter { it.meta?.profile == null } + val profiledDevices: List get() = devices.filter { it.meta?.profile != null } + val unmatchedDevices: List get() = devices.filter { it.meta?.profile == null } } fun onPermissionResult(@Suppress("UNUSED_PARAMETER") granted: Boolean) { diff --git a/app/src/main/java/eu/darken/capod/main/ui/overview/cards/DualPodsCard.kt b/app/src/main/java/eu/darken/capod/main/ui/overview/cards/DualPodsCard.kt index abe82d22..81380461 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/overview/cards/DualPodsCard.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/overview/cards/DualPodsCard.kt @@ -44,10 +44,10 @@ import eu.darken.capod.R import eu.darken.capod.common.compose.Preview2 import eu.darken.capod.common.compose.PreviewWrapper import eu.darken.capod.common.compose.preview.MockPodDataProvider -import eu.darken.capod.monitor.core.MonitoredDevice +import eu.darken.capod.monitor.core.PodDevice +import eu.darken.capod.monitor.core.firstSeenFormatted import eu.darken.capod.monitor.core.getSignalQuality import eu.darken.capod.monitor.core.lastSeenFormatted -import eu.darken.capod.monitor.core.firstSeenFormatted import eu.darken.capod.pods.core.HasPodStyle import eu.darken.capod.pods.core.HasStateDetection import eu.darken.capod.pods.core.apple.ApplePods @@ -61,7 +61,7 @@ import java.time.Instant @Composable fun DualPodsCard( - device: MonitoredDevice, + device: PodDevice, showDebug: Boolean, now: Instant, ) { @@ -316,7 +316,7 @@ private fun PodGauge( @OptIn(ExperimentalLayoutApi::class) @Composable private fun CaseRow( - device: MonitoredDevice, + device: PodDevice, ) { val context = LocalContext.current diff --git a/app/src/main/java/eu/darken/capod/main/ui/overview/cards/SinglePodsCard.kt b/app/src/main/java/eu/darken/capod/main/ui/overview/cards/SinglePodsCard.kt index c37aef33..ba3a7413 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/overview/cards/SinglePodsCard.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/overview/cards/SinglePodsCard.kt @@ -24,9 +24,9 @@ import androidx.compose.material.icons.twotone.BatteryChargingFull import androidx.compose.material.icons.twotone.Hearing import androidx.compose.material.icons.twotone.Key import androidx.compose.material3.CardDefaults -import androidx.compose.material3.Icon import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -44,10 +44,10 @@ import eu.darken.capod.R import eu.darken.capod.common.compose.Preview2 import eu.darken.capod.common.compose.PreviewWrapper import eu.darken.capod.common.compose.preview.MockPodDataProvider -import eu.darken.capod.monitor.core.MonitoredDevice +import eu.darken.capod.monitor.core.PodDevice +import eu.darken.capod.monitor.core.firstSeenFormatted import eu.darken.capod.monitor.core.getSignalQuality import eu.darken.capod.monitor.core.lastSeenFormatted -import eu.darken.capod.monitor.core.firstSeenFormatted import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.formatBatteryPercent import java.time.Duration @@ -56,7 +56,7 @@ import java.time.Instant @OptIn(ExperimentalLayoutApi::class) @Composable fun SinglePodsCard( - device: MonitoredDevice, + device: PodDevice, showDebug: Boolean, now: Instant, ) { diff --git a/app/src/main/java/eu/darken/capod/main/ui/overview/cards/UnknownPodDeviceCard.kt b/app/src/main/java/eu/darken/capod/main/ui/overview/cards/UnknownPodDeviceCard.kt index bded58e4..9d30a2cf 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/overview/cards/UnknownPodDeviceCard.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/overview/cards/UnknownPodDeviceCard.kt @@ -21,18 +21,18 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import eu.darken.capod.R -import eu.darken.capod.monitor.core.MonitoredDevice -import eu.darken.capod.monitor.core.getSignalQuality -import eu.darken.capod.monitor.core.lastSeenFormatted -import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.common.compose.Preview2 import eu.darken.capod.common.compose.PreviewWrapper import eu.darken.capod.common.compose.preview.MockPodDataProvider +import eu.darken.capod.monitor.core.PodDevice +import eu.darken.capod.monitor.core.getSignalQuality +import eu.darken.capod.monitor.core.lastSeenFormatted +import eu.darken.capod.pods.core.apple.ApplePods import java.time.Instant @Composable fun UnknownPodDeviceCard( - device: MonitoredDevice, + device: PodDevice, showDebug: Boolean, now: Instant, ) { diff --git a/app/src/main/java/eu/darken/capod/main/ui/widget/BatteryGlanceWidget.kt b/app/src/main/java/eu/darken/capod/main/ui/widget/BatteryGlanceWidget.kt index d99850bd..c9f877f3 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/widget/BatteryGlanceWidget.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/widget/BatteryGlanceWidget.kt @@ -2,6 +2,7 @@ package eu.darken.capod.main.ui.widget import android.appwidget.AppWidgetManager import android.content.Context +import androidx.annotation.Keep import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.glance.GlanceId @@ -10,7 +11,6 @@ import androidx.glance.appwidget.GlanceAppWidget import androidx.glance.appwidget.GlanceAppWidgetManager import androidx.glance.appwidget.SizeMode import androidx.glance.appwidget.provideContent -import androidx.annotation.Keep import dagger.hilt.EntryPoint import dagger.hilt.InstallIn import dagger.hilt.android.EntryPointAccessors @@ -23,7 +23,7 @@ import eu.darken.capod.common.debug.logging.logTag import eu.darken.capod.common.upgrade.UpgradeRepo import eu.darken.capod.common.upgrade.isPro import eu.darken.capod.monitor.core.DeviceMonitor -import eu.darken.capod.monitor.core.MonitoredDevice +import eu.darken.capod.monitor.core.PodDevice import eu.darken.capod.profiles.core.DeviceProfilesRepo class BatteryGlanceWidget : GlanceAppWidget() { @@ -45,7 +45,7 @@ class BatteryGlanceWidget : GlanceAppWidget() { val appWidgetId: Int val initialIsPro: Boolean val initialProfileId: String? - val cachedDevice: MonitoredDevice? + val cachedDevice: PodDevice? try { ep = EntryPointAccessors.fromApplication(context, WidgetEntryPoint::class.java) @@ -139,7 +139,7 @@ class BatteryGlanceWidget : GlanceAppWidget() { var n = 2 while (70 * n - 30 < size) { ++n -} + } return n - 1 } diff --git a/app/src/main/java/eu/darken/capod/main/ui/widget/WidgetRenderStateMapper.kt b/app/src/main/java/eu/darken/capod/main/ui/widget/WidgetRenderStateMapper.kt index 91ee49be..26627f99 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/widget/WidgetRenderStateMapper.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/widget/WidgetRenderStateMapper.kt @@ -4,7 +4,7 @@ import android.content.Context import androidx.annotation.ColorInt import androidx.appcompat.view.ContextThemeWrapper import eu.darken.capod.R -import eu.darken.capod.monitor.core.MonitoredDevice +import eu.darken.capod.monitor.core.PodDevice import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.getBatteryDrawable import eu.darken.capod.pods.core.toBatteryFloat @@ -13,7 +13,7 @@ object WidgetRenderStateMapper { fun map( context: Context, - device: MonitoredDevice?, + device: PodDevice?, theme: WidgetTheme, isPro: Boolean, hasConfiguredProfile: Boolean, diff --git a/app/src/main/java/eu/darken/capod/monitor/core/BlePodMonitor.kt b/app/src/main/java/eu/darken/capod/monitor/core/BlePodMonitor.kt index 19484a93..26be9e6d 100644 --- a/app/src/main/java/eu/darken/capod/monitor/core/BlePodMonitor.kt +++ b/app/src/main/java/eu/darken/capod/monitor/core/BlePodMonitor.kt @@ -1,12 +1,217 @@ package eu.darken.capod.monitor.core -/** - * Type alias for the BLE scan pipeline. - * - * The [PodMonitor] class handles BLE advertisement scanning and device detection. - * New code should use [BlePodMonitor] to clarify that this is the BLE-only monitor, - * not the unified merge point ([DeviceMonitor]). - * - * Full rename deferred to IDE refactoring pass. - */ -typealias BlePodMonitor = PodMonitor +import android.bluetooth.le.ScanFilter +import eu.darken.capod.common.bluetooth.BleScanner +import eu.darken.capod.common.bluetooth.BluetoothManager2 +import eu.darken.capod.common.bluetooth.ScannerMode +import eu.darken.capod.common.bluetooth.onlyNewAndUnique +import eu.darken.capod.common.coroutine.AppScope +import eu.darken.capod.common.debug.DebugSettings +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.asLog +import eu.darken.capod.common.debug.logging.log +import eu.darken.capod.common.debug.logging.logTag +import eu.darken.capod.common.flow.replayingShare +import eu.darken.capod.common.flow.throttleLatest +import eu.darken.capod.main.core.GeneralSettings +import eu.darken.capod.main.core.PermissionTool +import eu.darken.capod.pods.core.BlePodSnapshot +import eu.darken.capod.pods.core.PodFactory +import eu.darken.capod.pods.core.apple.protocol.ProximityPairing +import eu.darken.capod.profiles.core.DeviceProfilesRepo +import eu.darken.capod.profiles.core.currentProfiles +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.retryWhen +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import java.time.Duration +import java.time.Instant +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class BlePodMonitor @Inject constructor( + @AppScope private val appScope: CoroutineScope, + private val bleScanner: BleScanner, + private val podFactory: PodFactory, + private val generalSettings: GeneralSettings, + bluetoothManager: BluetoothManager2, + private val debugSettings: DebugSettings, + private val podDeviceCache: PodDeviceCache, + permissionTool: PermissionTool, + private val profilesRepo: DeviceProfilesRepo, +) { + + private val deviceCache = mutableMapOf() + private val cacheLock = Mutex() + + val devices: Flow> = combine( + permissionTool.missingScanPermissions, + bluetoothManager.isBluetoothEnabled + ) { missingScanPermissions, isBluetoothEnabled -> + log(TAG) { "devices: missingScanPermissions=$missingScanPermissions, isBluetoothEnabled=$isBluetoothEnabled" } + missingScanPermissions.isEmpty() && isBluetoothEnabled + } + .flatMapLatest { isReady -> + if (!isReady) { + log(TAG, WARN) { "Bluetooth is not ready" } + flowOf(null) + } else { + createBleScanner() + } + } + .map { results -> results?.mapNotNull { podFactory.createPod(it) } } + .map { processWithCache(it).values } + .flatMapLatest { devices -> + flowOf(sortPodsToInterest(devices)) + } + .retryWhen { cause, attempt -> + if (cause is SecurityException) { + log(TAG, WARN) { "PodMonitor failed due to missing permission, not retrying: ${cause.asLog()}" } + false + } else { + log(TAG, WARN) { "PodMonitor failed (attempt=$attempt), will retry: ${cause.asLog()}" } + delay(3000) + true + } + } + .onStart { emit(emptyList()) } + .replayingShare(appScope) + + private suspend fun sortPodsToInterest(devices: Collection): List { + val now = Instant.now() + val profiles = profilesRepo.currentProfiles() ?: emptyList() + + return devices.sortedWith( + compareBy { device -> + // Use profile position in list as priority (0 = highest priority) + device.meta.profile?.let { profile -> + profiles.indexOfFirst { it.id == profile.id }.takeIf { it >= 0 } ?: Int.MAX_VALUE + } ?: Int.MAX_VALUE + } + .thenBy { + val age = Duration.between(it.seenLastAt, now).seconds + if (age < 5) 0L else (age / 3L) + } + .thenByDescending { it.signalQuality } + .thenByDescending { (it.seenCounter / 10) } + ) + } + + private data class ScannerOptions( + val scannerMode: ScannerMode, + val showUnfiltered: Boolean, + val offloadedFilteringDisabled: Boolean, + val offloadedBatchingDisabled: Boolean, + val disableDirectCallback: Boolean, + ) + + private fun createBleScanner() = combine( + generalSettings.scannerMode.flow, + debugSettings.showUnfiltered.flow, + generalSettings.isOffloadedBatchingDisabled.flow, + generalSettings.isOffloadedFilteringDisabled.flow, + generalSettings.useIndirectScanResultCallback.flow, + ) { + scannermode, + showUnfiltered, + isOffloadedBatchingDisabled, + isOffloadedFilteringDisabled, + useIndirectScanResultCallback, + -> + ScannerOptions( + scannerMode = scannermode, + showUnfiltered = showUnfiltered, + offloadedFilteringDisabled = isOffloadedFilteringDisabled, + offloadedBatchingDisabled = isOffloadedBatchingDisabled, + disableDirectCallback = useIndirectScanResultCallback, + ) + } + .throttleLatest(1000) + .flatMapLatest { options -> + val filters = when { + options.showUnfiltered -> { + log(TAG, WARN) { "Using unfiltered scan mode" } + setOf(ScanFilter.Builder().build()) + } + + else -> ProximityPairing.getBleScanFilter() + } + + bleScanner.scan( + filters = filters, + scannerMode = options.scannerMode, + disableOffloadFiltering = options.offloadedFilteringDisabled, + disableOffloadBatching = options.offloadedBatchingDisabled, + disableDirectScanCallback = options.disableDirectCallback, + ) + } + .map { it.onlyNewAndUnique() } + + private suspend fun processWithCache( + newPods: List? + ): Map = cacheLock.withLock { + if (newPods == null) { + log(TAG) { "Null result, Bluetooth is disabled." } + deviceCache.clear() + return emptyMap() + } + + val now = Instant.now() + deviceCache.toList().forEach { (key, value) -> + if (Duration.between(value.seenLastAt, now) > Duration.ofSeconds(20)) { + log(TAG, VERBOSE) { "Removing stale device from cache: $value" } + deviceCache.remove(key) + } + } + + val pods = mutableMapOf() + + pods.putAll(deviceCache) + + newPods.map { it.device }.forEach { + deviceCache[it.identifier] = it + pods[it.identifier] = it + } + + newPods + .mapNotNull { + val profileId = it.device.meta.profile?.id ?: return@mapNotNull null + profileId to it.device.scanResult + } + .toMap() + .run { podDeviceCache.saveAll(this) } + return pods + } + + suspend fun getDeviceForProfile(profileId: String): BlePodSnapshot? { + log(TAG) { "getDeviceForProfile(profileId=$profileId)" } + + val liveDevice = devices.firstOrNull()?.firstOrNull { device -> + device.meta.profile?.id == profileId + } + if (liveDevice != null) { + log(TAG) { "Found live device for profile $profileId: $liveDevice" } + return liveDevice + } + + val cachedDevice = podDeviceCache.load(profileId)?.let { + podFactory.createPod(it)?.device + } + log(TAG) { "Cached device for profile $profileId: $cachedDevice" } + return cachedDevice + } + + companion object { + private val TAG = logTag("Monitor", "PodMonitor") + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/darken/capod/monitor/core/DeviceMonitor.kt b/app/src/main/java/eu/darken/capod/monitor/core/DeviceMonitor.kt index a7bc8a4a..7ae0801a 100644 --- a/app/src/main/java/eu/darken/capod/monitor/core/DeviceMonitor.kt +++ b/app/src/main/java/eu/darken/capod/monitor/core/DeviceMonitor.kt @@ -11,7 +11,7 @@ import javax.inject.Singleton /** * Single merge point: combines BLE scan data ([BlePodMonitor]) with AAP connection data - * ([AapConnectionManager]) into unified [MonitoredDevice] objects. + * ([AapConnectionManager]) into unified [PodDevice] objects. * * ViewModels should observe [devices] instead of accessing BlePodMonitor directly. */ @@ -20,21 +20,21 @@ class DeviceMonitor @Inject constructor( private val blePodMonitor: BlePodMonitor, private val aapManager: AapConnectionManager, ) { - val devices: Flow> = blePodMonitor.devices + val devices: Flow> = blePodMonitor.devices .combine(aapManager.allStates) { pods, aapStates -> pods.map { pod -> - MonitoredDevice( + PodDevice( ble = pod, aap = aapStates[pod.address], ) } } - suspend fun getDeviceForProfile(profileId: String): MonitoredDevice? { + suspend fun getDeviceForProfile(profileId: String): PodDevice? { log(TAG) { "getDeviceForProfile(profileId=$profileId)" } val bleDevice = blePodMonitor.getDeviceForProfile(profileId) ?: return null val aapState = aapManager.allStates.firstOrNull()?.get(bleDevice.address) - return MonitoredDevice(ble = bleDevice, aap = aapState) + return PodDevice(ble = bleDevice, aap = aapState) } companion object { diff --git a/app/src/main/java/eu/darken/capod/monitor/core/DeviceMonitorExtensions.kt b/app/src/main/java/eu/darken/capod/monitor/core/DeviceMonitorExtensions.kt index 35a1bda3..0617c1d9 100644 --- a/app/src/main/java/eu/darken/capod/monitor/core/DeviceMonitorExtensions.kt +++ b/app/src/main/java/eu/darken/capod/monitor/core/DeviceMonitorExtensions.kt @@ -8,17 +8,17 @@ import java.time.Duration import java.time.Instant import kotlin.math.roundToInt -fun DeviceMonitor.devicesWithProfiles(): Flow> = devices +fun DeviceMonitor.devicesWithProfiles(): Flow> = devices .map { devices -> devices.filter { it.meta?.profile != null } } -fun DeviceMonitor.primaryDevice(): Flow = devicesWithProfiles().map { it.firstOrNull() } +fun DeviceMonitor.primaryDevice(): Flow = devicesWithProfiles().map { it.firstOrNull() } -fun MonitoredDevice.getSignalQuality(context: Context): String { +fun PodDevice.getSignalQuality(context: Context): String { val multiplier = 100 * signalQuality return "${multiplier.roundToInt()}%" } -fun MonitoredDevice.lastSeenFormatted(now: Instant): String { +fun PodDevice.lastSeenFormatted(now: Instant): String { val lastAt = seenLastAt ?: return "" val formatter = RelativeDateTimeFormatter.getInstance() val duration = Duration.between(lastAt, now) @@ -37,7 +37,7 @@ fun MonitoredDevice.lastSeenFormatted(now: Instant): String { } } -fun MonitoredDevice.firstSeenFormatted(now: Instant): String { +fun PodDevice.firstSeenFormatted(now: Instant): String { val firstAt = seenFirstAt ?: return "" val formatter = RelativeDateTimeFormatter.getInstance() val duration = Duration.between(firstAt, now) diff --git a/app/src/main/java/eu/darken/capod/monitor/core/MonitoredDevice.kt b/app/src/main/java/eu/darken/capod/monitor/core/PodDevice.kt similarity index 86% rename from app/src/main/java/eu/darken/capod/monitor/core/MonitoredDevice.kt rename to app/src/main/java/eu/darken/capod/monitor/core/PodDevice.kt index 1fda6a50..30a3838e 100644 --- a/app/src/main/java/eu/darken/capod/monitor/core/MonitoredDevice.kt +++ b/app/src/main/java/eu/darken/capod/monitor/core/PodDevice.kt @@ -3,16 +3,15 @@ package eu.darken.capod.monitor.core import android.content.Context import eu.darken.capod.common.bluetooth.BluetoothAddress import eu.darken.capod.pods.core.BlePodSnapshot -import eu.darken.capod.pods.core.DualPodDevice +import eu.darken.capod.pods.core.DualBlePodSnapshot import eu.darken.capod.pods.core.HasCase import eu.darken.capod.pods.core.HasChargeDetection import eu.darken.capod.pods.core.HasChargeDetectionDual import eu.darken.capod.pods.core.HasDualMicrophone import eu.darken.capod.pods.core.HasEarDetection import eu.darken.capod.pods.core.HasEarDetectionDual -import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.PodModel -import eu.darken.capod.pods.core.SinglePodDevice +import eu.darken.capod.pods.core.SingleBlePodSnapshot import eu.darken.capod.pods.core.apple.DualApplePods import eu.darken.capod.pods.core.apple.protocol.aap.AapPodState import eu.darken.capod.pods.core.apple.protocol.aap.AapSetting @@ -23,15 +22,15 @@ import java.time.Instant * All properties are dynamically resolved from the best available source. * Consumers don't need to know whether data came from BLE or AAP. */ -data class MonitoredDevice( +data class PodDevice( internal val ble: BlePodSnapshot?, internal val aap: AapPodState?, ) { // Identity val model: PodModel get() = ble?.model ?: PodModel.UNKNOWN val address: BluetoothAddress? get() = ble?.address - val identifier: PodDevice.Id? get() = ble?.identifier - val meta: PodDevice.Meta? get() = ble?.meta + val identifier: BlePodSnapshot.Id? get() = ble?.identifier + val meta: BlePodSnapshot.Meta? get() = ble?.meta // Capabilities from Model.Features val hasCase: Boolean get() = model.features.hasCase @@ -48,16 +47,16 @@ data class MonitoredDevice( // Battery — best available source val batteryLeft: Float? - get() = (ble as? DualPodDevice)?.batteryLeftPodPercent + get() = (ble as? DualBlePodSnapshot)?.batteryLeftPodPercent val batteryRight: Float? - get() = (ble as? DualPodDevice)?.batteryRightPodPercent + get() = (ble as? DualBlePodSnapshot)?.batteryRightPodPercent val batteryCase: Float? get() = (ble as? HasCase)?.batteryCasePercent val batteryHeadset: Float? - get() = (ble as? SinglePodDevice)?.batteryHeadsetPercent + get() = (ble as? SingleBlePodSnapshot)?.batteryHeadsetPercent // Charging val isLeftPodCharging: Boolean? @@ -99,10 +98,10 @@ data class MonitoredDevice( val iconRes: Int get() = ble?.iconRes ?: model.iconRes val leftPodIcon: Int? - get() = (ble as? DualPodDevice)?.leftPodIcon + get() = (ble as? DualBlePodSnapshot)?.leftPodIcon val rightPodIcon: Int? - get() = (ble as? DualPodDevice)?.rightPodIcon + get() = (ble as? DualBlePodSnapshot)?.rightPodIcon val caseIcon: Int? get() = (ble as? HasCase)?.caseIcon diff --git a/app/src/main/java/eu/darken/capod/monitor/core/PodDeviceCache.kt b/app/src/main/java/eu/darken/capod/monitor/core/PodDeviceCache.kt index 0a852417..ca69209e 100644 --- a/app/src/main/java/eu/darken/capod/monitor/core/PodDeviceCache.kt +++ b/app/src/main/java/eu/darken/capod/monitor/core/PodDeviceCache.kt @@ -78,7 +78,7 @@ class PodDeviceCache @Inject constructor( } companion object { - private val TAG = logTag("Monitor", "PodMonitor", "Cache") + private val TAG = logTag("Monitor", "BlePodMonitor", "Cache") } } diff --git a/app/src/main/java/eu/darken/capod/monitor/core/PodMonitor.kt b/app/src/main/java/eu/darken/capod/monitor/core/PodMonitor.kt deleted file mode 100644 index ed86d89d..00000000 --- a/app/src/main/java/eu/darken/capod/monitor/core/PodMonitor.kt +++ /dev/null @@ -1,217 +0,0 @@ -package eu.darken.capod.monitor.core - -import android.bluetooth.le.ScanFilter -import eu.darken.capod.common.bluetooth.BleScanner -import eu.darken.capod.common.bluetooth.BluetoothManager2 -import eu.darken.capod.common.bluetooth.ScannerMode -import eu.darken.capod.common.bluetooth.onlyNewAndUnique -import eu.darken.capod.common.coroutine.AppScope -import eu.darken.capod.common.debug.DebugSettings -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.asLog -import eu.darken.capod.common.debug.logging.log -import eu.darken.capod.common.debug.logging.logTag -import eu.darken.capod.common.flow.replayingShare -import eu.darken.capod.common.flow.throttleLatest -import eu.darken.capod.main.core.GeneralSettings -import eu.darken.capod.main.core.PermissionTool -import eu.darken.capod.pods.core.PodDevice -import eu.darken.capod.pods.core.PodFactory -import eu.darken.capod.pods.core.apple.protocol.ProximityPairing -import eu.darken.capod.profiles.core.DeviceProfilesRepo -import eu.darken.capod.profiles.core.currentProfiles -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.retryWhen -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import java.time.Duration -import java.time.Instant -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class PodMonitor @Inject constructor( - @AppScope private val appScope: CoroutineScope, - private val bleScanner: BleScanner, - private val podFactory: PodFactory, - private val generalSettings: GeneralSettings, - bluetoothManager: BluetoothManager2, - private val debugSettings: DebugSettings, - private val podDeviceCache: PodDeviceCache, - permissionTool: PermissionTool, - private val profilesRepo: DeviceProfilesRepo, -) { - - private val deviceCache = mutableMapOf() - private val cacheLock = Mutex() - - val devices: Flow> = combine( - permissionTool.missingScanPermissions, - bluetoothManager.isBluetoothEnabled - ) { missingScanPermissions, isBluetoothEnabled -> - log(TAG) { "devices: missingScanPermissions=$missingScanPermissions, isBluetoothEnabled=$isBluetoothEnabled" } - missingScanPermissions.isEmpty() && isBluetoothEnabled - } - .flatMapLatest { isReady -> - if (!isReady) { - log(TAG, WARN) { "Bluetooth is not ready" } - flowOf(null) - } else { - createBleScanner() - } - } - .map { results -> results?.mapNotNull { podFactory.createPod(it) } } - .map { processWithCache(it).values } - .flatMapLatest { devices -> - flowOf(sortPodsToInterest(devices)) - } - .retryWhen { cause, attempt -> - if (cause is SecurityException) { - log(TAG, WARN) { "PodMonitor failed due to missing permission, not retrying: ${cause.asLog()}" } - false - } else { - log(TAG, WARN) { "PodMonitor failed (attempt=$attempt), will retry: ${cause.asLog()}" } - delay(3000) - true - } - } - .onStart { emit(emptyList()) } - .replayingShare(appScope) - - private suspend fun sortPodsToInterest(devices: Collection): List { - val now = Instant.now() - val profiles = profilesRepo.currentProfiles() ?: emptyList() - - return devices.sortedWith( - compareBy { device -> - // Use profile position in list as priority (0 = highest priority) - device.meta.profile?.let { profile -> - profiles.indexOfFirst { it.id == profile.id }.takeIf { it >= 0 } ?: Int.MAX_VALUE - } ?: Int.MAX_VALUE - } - .thenBy { - val age = Duration.between(it.seenLastAt, now).seconds - if (age < 5) 0L else (age / 3L) - } - .thenByDescending { it.signalQuality } - .thenByDescending { (it.seenCounter / 10) } - ) - } - - private data class ScannerOptions( - val scannerMode: ScannerMode, - val showUnfiltered: Boolean, - val offloadedFilteringDisabled: Boolean, - val offloadedBatchingDisabled: Boolean, - val disableDirectCallback: Boolean, - ) - - private fun createBleScanner() = combine( - generalSettings.scannerMode.flow, - debugSettings.showUnfiltered.flow, - generalSettings.isOffloadedBatchingDisabled.flow, - generalSettings.isOffloadedFilteringDisabled.flow, - generalSettings.useIndirectScanResultCallback.flow, - ) { - scannermode, - showUnfiltered, - isOffloadedBatchingDisabled, - isOffloadedFilteringDisabled, - useIndirectScanResultCallback, - -> - ScannerOptions( - scannerMode = scannermode, - showUnfiltered = showUnfiltered, - offloadedFilteringDisabled = isOffloadedFilteringDisabled, - offloadedBatchingDisabled = isOffloadedBatchingDisabled, - disableDirectCallback = useIndirectScanResultCallback, - ) - } - .throttleLatest(1000) - .flatMapLatest { options -> - val filters = when { - options.showUnfiltered -> { - log(TAG, WARN) { "Using unfiltered scan mode" } - setOf(ScanFilter.Builder().build()) - } - - else -> ProximityPairing.getBleScanFilter() - } - - bleScanner.scan( - filters = filters, - scannerMode = options.scannerMode, - disableOffloadFiltering = options.offloadedFilteringDisabled, - disableOffloadBatching = options.offloadedBatchingDisabled, - disableDirectScanCallback = options.disableDirectCallback, - ) - } - .map { it.onlyNewAndUnique() } - - private suspend fun processWithCache( - newPods: List? - ): Map = cacheLock.withLock { - if (newPods == null) { - log(TAG) { "Null result, Bluetooth is disabled." } - deviceCache.clear() - return emptyMap() - } - - val now = Instant.now() - deviceCache.toList().forEach { (key, value) -> - if (Duration.between(value.seenLastAt, now) > Duration.ofSeconds(20)) { - log(TAG, VERBOSE) { "Removing stale device from cache: $value" } - deviceCache.remove(key) - } - } - - val pods = mutableMapOf() - - pods.putAll(deviceCache) - - newPods.map { it.device }.forEach { - deviceCache[it.identifier] = it - pods[it.identifier] = it - } - - newPods - .mapNotNull { - val profileId = it.device.meta.profile?.id ?: return@mapNotNull null - profileId to it.device.scanResult - } - .toMap() - .run { podDeviceCache.saveAll(this) } - return pods - } - - suspend fun getDeviceForProfile(profileId: String): PodDevice? { - log(TAG) { "getDeviceForProfile(profileId=$profileId)" } - - val liveDevice = devices.firstOrNull()?.firstOrNull { device -> - device.meta.profile?.id == profileId - } - if (liveDevice != null) { - log(TAG) { "Found live device for profile $profileId: $liveDevice" } - return liveDevice - } - - val cachedDevice = podDeviceCache.load(profileId)?.let { - podFactory.createPod(it)?.device - } - log(TAG) { "Cached device for profile $profileId: $cachedDevice" } - return cachedDevice - } - - companion object { - private val TAG = logTag("Monitor", "PodMonitor") - } -} \ No newline at end of file diff --git a/app/src/main/java/eu/darken/capod/monitor/core/PodMonitorExtensions.kt b/app/src/main/java/eu/darken/capod/monitor/core/PodMonitorExtensions.kt index 89b8cb3d..545982aa 100644 --- a/app/src/main/java/eu/darken/capod/monitor/core/PodMonitorExtensions.kt +++ b/app/src/main/java/eu/darken/capod/monitor/core/PodMonitorExtensions.kt @@ -1,11 +1,10 @@ package eu.darken.capod.monitor.core -import eu.darken.capod.pods.core.PodDevice +import eu.darken.capod.pods.core.BlePodSnapshot import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -import kotlin.collections.firstOrNull -fun PodMonitor.devicesWithProfiles(): Flow> = devices +fun BlePodMonitor.devicesWithProfiles(): Flow> = devices .map { devices -> devices.filter { it.meta.profile != null } } -fun PodMonitor.primaryDevice(): Flow = devicesWithProfiles().map { it.firstOrNull() } \ No newline at end of file +fun BlePodMonitor.primaryDevice(): Flow = devicesWithProfiles().map { it.firstOrNull() } \ No newline at end of file diff --git a/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorService.kt b/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorService.kt index d9d5c462..e9fed4db 100644 --- a/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorService.kt +++ b/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorService.kt @@ -14,6 +14,7 @@ import dagger.hilt.android.AndroidEntryPoint import eu.darken.capod.common.bluetooth.BluetoothDevice2 import eu.darken.capod.common.bluetooth.BluetoothManager2 import eu.darken.capod.common.coroutine.DispatcherProvider +import eu.darken.capod.common.datastore.valueBlocking import eu.darken.capod.common.debug.Bugs import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE import eu.darken.capod.common.debug.logging.Logging.Priority.WARN @@ -26,9 +27,9 @@ import eu.darken.capod.common.hasApiLevel import eu.darken.capod.main.core.GeneralSettings import eu.darken.capod.main.core.MonitorMode import eu.darken.capod.main.core.PermissionTool +import eu.darken.capod.monitor.core.BlePodMonitor import eu.darken.capod.monitor.core.DeviceMonitor 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.profiles.core.DeviceProfile @@ -54,7 +55,6 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import javax.inject.Inject -import eu.darken.capod.common.datastore.valueBlocking @AndroidEntryPoint class MonitorService : Service() { @@ -64,7 +64,7 @@ class MonitorService : Service() { @Inject lateinit var notificationManager: NotificationManager @Inject lateinit var generalSettings: GeneralSettings @Inject lateinit var permissionTool: PermissionTool - @Inject lateinit var podMonitor: PodMonitor + @Inject lateinit var blePodMonitor: BlePodMonitor @Inject lateinit var deviceMonitor: DeviceMonitor @Inject lateinit var bluetoothManager: BluetoothManager2 @Inject lateinit var playPause: PlayPause @@ -179,7 +179,7 @@ class MonitorService : Service() { } val monitorJob = deviceMonitor.primaryDevice() - .setupCommonEventHandlers(TAG) { "PodMonitor" } + .setupCommonEventHandlers(TAG) { "BlePodMonitor" } .distinctUntilChanged() .throttleLatest(1000) .onEach { currentDevice -> @@ -222,6 +222,7 @@ class MonitorService : Service() { @Suppress("UNCHECKED_CAST") val profiles = arguments[1] as List + @Suppress("UNCHECKED_CAST") val devices = arguments[2] as Collection diff --git a/app/src/main/java/eu/darken/capod/monitor/ui/MonitorNotificationViewFactory.kt b/app/src/main/java/eu/darken/capod/monitor/ui/MonitorNotificationViewFactory.kt index ec1c14f1..2d945cd8 100644 --- a/app/src/main/java/eu/darken/capod/monitor/ui/MonitorNotificationViewFactory.kt +++ b/app/src/main/java/eu/darken/capod/monitor/ui/MonitorNotificationViewFactory.kt @@ -5,7 +5,7 @@ import android.view.View import android.widget.RemoteViews import dagger.hilt.android.qualifiers.ApplicationContext import eu.darken.capod.R -import eu.darken.capod.monitor.core.MonitoredDevice +import eu.darken.capod.monitor.core.PodDevice import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.formatBatteryPercent import eu.darken.capod.pods.core.getBatteryDrawable @@ -17,13 +17,13 @@ class MonitorNotificationViewFactory @Inject constructor( @ApplicationContext private val context: Context ) { - fun createContentView(device: MonitoredDevice): RemoteViews = when { + fun createContentView(device: PodDevice): RemoteViews = when { device.hasDualPods -> createDualPods(device) device.model != PodModel.UNKNOWN -> createSinglePod(device) else -> createUnknownDevice(device) } - private fun createDualPods(device: MonitoredDevice): RemoteViews = RemoteViews( + private fun createDualPods(device: PodDevice): RemoteViews = RemoteViews( context.packageName, R.layout.monitor_notification_dual_pods_small ).apply { @@ -55,7 +55,7 @@ class MonitorNotificationViewFactory @Inject constructor( setViewVisibility(R.id.pod_right_ear, if (isRightPodInEar) View.VISIBLE else View.GONE) } - private fun createSinglePod(device: MonitoredDevice): RemoteViews = RemoteViews( + private fun createSinglePod(device: PodDevice): RemoteViews = RemoteViews( context.packageName, R.layout.monitor_notification_single_pods_small ).apply { @@ -68,24 +68,27 @@ class MonitorNotificationViewFactory @Inject constructor( setViewVisibility(R.id.headphones_worn, if (device.isBeingWorn == true) View.VISIBLE else View.GONE) } if (device.isHeadsetBeingCharged != null) { - setViewVisibility(R.id.headphones_charging, if (device.isHeadsetBeingCharged == true) View.VISIBLE else View.GONE) + setViewVisibility( + R.id.headphones_charging, + if (device.isHeadsetBeingCharged == true) View.VISIBLE else View.GONE + ) } } - private fun createUnknownDevice(device: MonitoredDevice): RemoteViews = RemoteViews( + private fun createUnknownDevice(device: PodDevice): RemoteViews = RemoteViews( context.packageName, R.layout.monitor_notification_unknown_device_small ).apply { setTextViewText(R.id.device, device.getLabel(context)) } - fun createBigContentView(device: MonitoredDevice): RemoteViews = when { + fun createBigContentView(device: PodDevice): RemoteViews = when { device.hasDualPods -> createDualPodsBig(device) device.model != PodModel.UNKNOWN -> createSinglePodBig(device) else -> createUnknownDeviceBig(device) } - private fun createDualPodsBig(device: MonitoredDevice): RemoteViews = RemoteViews( + private fun createDualPodsBig(device: PodDevice): RemoteViews = RemoteViews( context.packageName, R.layout.monitor_notification_dual_pods_big ).apply { @@ -120,7 +123,7 @@ class MonitorNotificationViewFactory @Inject constructor( setViewVisibility(R.id.pod_right_ear, if (isRightPodInEar) View.VISIBLE else View.GONE) } - private fun createSinglePodBig(device: MonitoredDevice): RemoteViews = RemoteViews( + private fun createSinglePodBig(device: PodDevice): RemoteViews = RemoteViews( context.packageName, R.layout.monitor_notification_single_pods_big ).apply { @@ -133,11 +136,14 @@ class MonitorNotificationViewFactory @Inject constructor( setViewVisibility(R.id.headphones_worn, if (device.isBeingWorn == true) View.VISIBLE else View.GONE) } if (device.isHeadsetBeingCharged != null) { - setViewVisibility(R.id.headphones_charging, if (device.isHeadsetBeingCharged == true) View.VISIBLE else View.GONE) + setViewVisibility( + R.id.headphones_charging, + if (device.isHeadsetBeingCharged == true) View.VISIBLE else View.GONE + ) } } - private fun createUnknownDeviceBig(device: MonitoredDevice): RemoteViews = RemoteViews( + private fun createUnknownDeviceBig(device: PodDevice): RemoteViews = RemoteViews( context.packageName, R.layout.monitor_notification_unknown_device_big ).apply { diff --git a/app/src/main/java/eu/darken/capod/monitor/ui/MonitorNotifications.kt b/app/src/main/java/eu/darken/capod/monitor/ui/MonitorNotifications.kt index 268413a0..9ab78e7e 100644 --- a/app/src/main/java/eu/darken/capod/monitor/ui/MonitorNotifications.kt +++ b/app/src/main/java/eu/darken/capod/monitor/ui/MonitorNotifications.kt @@ -15,7 +15,7 @@ import eu.darken.capod.common.debug.logging.log import eu.darken.capod.common.debug.logging.logTag import eu.darken.capod.common.notifications.PendingIntentCompat import eu.darken.capod.main.ui.MainActivity -import eu.darken.capod.monitor.core.MonitoredDevice +import eu.darken.capod.monitor.core.PodDevice import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.formatBatteryPercent import javax.inject.Inject @@ -53,7 +53,11 @@ class MonitorNotifications @Inject constructor( setOngoing(true) } - private fun getBuilder(device: MonitoredDevice?, channelId: String, showHint: Boolean = false): NotificationCompat.Builder { + private fun getBuilder( + device: PodDevice?, + channelId: String, + showHint: Boolean = false + ): NotificationCompat.Builder { if (device == null) { return baseBuilder(channelId).apply { if (showHint) { @@ -122,10 +126,10 @@ class MonitorNotifications @Inject constructor( } } - fun getNotification(podDevice: MonitoredDevice?, showHint: Boolean = false): Notification = + fun getNotification(podDevice: PodDevice?, showHint: Boolean = false): Notification = getBuilder(podDevice, NOTIFICATION_CHANNEL_ID, showHint).build() - fun getNotificationConnected(podDevice: MonitoredDevice?): Notification = + fun getNotificationConnected(podDevice: PodDevice?): Notification = getBuilder(podDevice, NOTIFICATION_CHANNEL_ID_CONNECTED).build() fun getStartupNotification(): Notification = diff --git a/app/src/main/java/eu/darken/capod/pods/core/BlePodSnapshot.kt b/app/src/main/java/eu/darken/capod/pods/core/BlePodSnapshot.kt index 91d6404d..bc343d57 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/BlePodSnapshot.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/BlePodSnapshot.kt @@ -1,15 +1,77 @@ package eu.darken.capod.pods.core -/** - * Type alias for the BLE-sourced device snapshot. - * - * The [PodDevice] interface represents BLE scan data (battery, ear detection, case state) - * extracted from Apple Continuity Protocol advertisements. - * - * New code should use [BlePodSnapshot] to clarify that this is the BLE data source, - * not the unified facade ([eu.darken.capod.monitor.core.MonitoredDevice]). - * - * Full rename of [PodDevice] → [BlePodSnapshot] across the codebase is deferred to - * an IDE refactoring pass (103 files, 470 occurrences). - */ -typealias BlePodSnapshot = PodDevice +import android.content.Context +import androidx.annotation.DrawableRes +import eu.darken.capod.common.bluetooth.BleScanResult +import eu.darken.capod.common.bluetooth.BluetoothAddress +import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE +import eu.darken.capod.common.debug.logging.log +import eu.darken.capod.profiles.core.DeviceProfile +import java.time.Duration +import java.time.Instant +import java.util.UUID +import kotlin.math.abs +import kotlin.math.max + +interface BlePodSnapshot { + + val identifier: Id + + val model: PodModel + + val address: BluetoothAddress + get() = scanResult.address + + val seenLastAt: Instant + + val seenFirstAt: Instant + + val seenCounter: Int + + val scanResult: BleScanResult + + val rssi: Int + get() = scanResult.rssi + + val reliability: Float + + val signalQuality: Float + get() { + /** + * This is not correct but it works ¯\_(ツ)_/¯ + * The range of the RSSI is device specific (ROMs). + */ + val sqRssi = ((100 - abs(rssi)) / 100f) + val sqReliability = max(BASE_CONFIDENCE, reliability) + val sqAge = (Duration.between(seenFirstAt, Instant.now()).toMinutes().coerceAtMost(60) / 60f) * 0.25f + log(VERBOSE) { "Signal Quality ($address): rssi=$sqRssi, reliability=$reliability, age=$sqAge" } + return (sqRssi + sqReliability + sqAge) / 2f + } + + val rawData: Map + get() = scanResult.manufacturerSpecificData + + val rawDataHex: List + get() = rawData.entries.map { entry -> + "${entry.key}: ${entry.value.joinToString(separator = " ") { String.format("%02X", it) }}" + } + + interface Meta { + val profile: DeviceProfile? + } + + val meta: Meta + + fun getLabel(context: Context): String = model.label + + @get:DrawableRes + val iconRes: Int + get() = model.iconRes + + @JvmInline + value class Id(private val id: UUID = UUID.randomUUID()) + + companion object { + const val BASE_CONFIDENCE = 0.0f + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/darken/capod/pods/core/DualPodDevice.kt b/app/src/main/java/eu/darken/capod/pods/core/DualBlePodSnapshot.kt similarity index 90% rename from app/src/main/java/eu/darken/capod/pods/core/DualPodDevice.kt rename to app/src/main/java/eu/darken/capod/pods/core/DualBlePodSnapshot.kt index 3397ee21..0d2efd08 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/DualPodDevice.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/DualBlePodSnapshot.kt @@ -3,7 +3,7 @@ package eu.darken.capod.pods.core import androidx.annotation.DrawableRes import eu.darken.capod.R -interface DualPodDevice : PodDevice { +interface DualBlePodSnapshot : BlePodSnapshot { enum class Pod { LEFT, diff --git a/app/src/main/java/eu/darken/capod/pods/core/PodDevice.kt b/app/src/main/java/eu/darken/capod/pods/core/PodDevice.kt deleted file mode 100644 index 3fc07dbe..00000000 --- a/app/src/main/java/eu/darken/capod/pods/core/PodDevice.kt +++ /dev/null @@ -1,77 +0,0 @@ -package eu.darken.capod.pods.core - -import android.content.Context -import androidx.annotation.DrawableRes -import eu.darken.capod.common.bluetooth.BleScanResult -import eu.darken.capod.common.bluetooth.BluetoothAddress -import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE -import eu.darken.capod.common.debug.logging.log -import eu.darken.capod.profiles.core.DeviceProfile -import java.time.Duration -import java.time.Instant -import java.util.UUID -import kotlin.math.abs -import kotlin.math.max - -interface PodDevice { - - val identifier: Id - - val model: PodModel - - val address: BluetoothAddress - get() = scanResult.address - - val seenLastAt: Instant - - val seenFirstAt: Instant - - val seenCounter: Int - - val scanResult: BleScanResult - - val rssi: Int - get() = scanResult.rssi - - val reliability: Float - - val signalQuality: Float - get() { - /** - * This is not correct but it works ¯\_(ツ)_/¯ - * The range of the RSSI is device specific (ROMs). - */ - val sqRssi = ((100 - abs(rssi)) / 100f) - val sqReliability = max(BASE_CONFIDENCE, reliability) - val sqAge = (Duration.between(seenFirstAt, Instant.now()).toMinutes().coerceAtMost(60) / 60f) * 0.25f - log(VERBOSE) { "Signal Quality ($address): rssi=$sqRssi, reliability=$reliability, age=$sqAge" } - return (sqRssi + sqReliability + sqAge) / 2f - } - - val rawData: Map - get() = scanResult.manufacturerSpecificData - - val rawDataHex: List - get() = rawData.entries.map { entry -> - "${entry.key}: ${entry.value.joinToString(separator = " ") { String.format("%02X", it) }}" - } - - interface Meta { - val profile: DeviceProfile? - } - - val meta: Meta - - fun getLabel(context: Context): String = model.label - - @get:DrawableRes - val iconRes: Int - get() = model.iconRes - - @JvmInline - value class Id(private val id: UUID = UUID.randomUUID()) - - companion object { - const val BASE_CONFIDENCE = 0.0f - } -} \ No newline at end of file diff --git a/app/src/main/java/eu/darken/capod/pods/core/PodDeviceExtensions.kt b/app/src/main/java/eu/darken/capod/pods/core/PodDeviceExtensions.kt index a99c4095..c6e1c005 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/PodDeviceExtensions.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/PodDeviceExtensions.kt @@ -18,7 +18,7 @@ fun formatBatteryPercent(context: Context, percent: Float?): String = percent?.let { "${(it * 100).roundToInt()}%" } ?: context.getString(R.string.general_value_not_available_label) -fun PodDevice.getSignalQuality(context: Context): String { +fun BlePodSnapshot.getSignalQuality(context: Context): String { val multiplier = 100 * signalQuality return "${multiplier.roundToInt()}%" } @@ -36,7 +36,7 @@ fun getBatteryDrawable(percent: Float?): Int = when { else -> R.drawable.ic_baseline_battery_0_bar_24 } -fun PodDevice.lastSeenFormatted(now: Instant): String { +fun BlePodSnapshot.lastSeenFormatted(now: Instant): String { val formatter = RelativeDateTimeFormatter.getInstance() val duration = Duration.between(seenLastAt, now) return if (duration > Duration.ofMinutes(1)) { @@ -54,7 +54,7 @@ fun PodDevice.lastSeenFormatted(now: Instant): String { } } -fun PodDevice.firstSeenFormatted(now: Instant): String { +fun BlePodSnapshot.firstSeenFormatted(now: Instant): String { val formatter = RelativeDateTimeFormatter.getInstance() val duration = Duration.between(seenFirstAt, now) return formatter.format( diff --git a/app/src/main/java/eu/darken/capod/pods/core/PodFactory.kt b/app/src/main/java/eu/darken/capod/pods/core/PodFactory.kt index 9f88ddf3..2a6315a7 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/PodFactory.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/PodFactory.kt @@ -36,7 +36,7 @@ class PodFactory @Inject constructor( data class Result( val scanResult: BleScanResult, - val device: PodDevice + val device: BlePodSnapshot ) companion object { diff --git a/app/src/main/java/eu/darken/capod/pods/core/SinglePodDevice.kt b/app/src/main/java/eu/darken/capod/pods/core/SingleBlePodSnapshot.kt similarity index 60% rename from app/src/main/java/eu/darken/capod/pods/core/SinglePodDevice.kt rename to app/src/main/java/eu/darken/capod/pods/core/SingleBlePodSnapshot.kt index d00a1176..fbd7ce58 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/SinglePodDevice.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/SingleBlePodSnapshot.kt @@ -1,6 +1,6 @@ package eu.darken.capod.pods.core -interface SinglePodDevice : PodDevice { +interface SingleBlePodSnapshot : BlePodSnapshot { val batteryHeadsetPercent: Float? } \ No newline at end of file diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/AppleFactory.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/AppleFactory.kt index b36a16f7..4df537c6 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/AppleFactory.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/AppleFactory.kt @@ -6,9 +6,9 @@ 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.pods.core.PodDevice +import eu.darken.capod.pods.core.BlePodSnapshot import eu.darken.capod.pods.core.PodModel -import eu.darken.capod.pods.core.apple.misc.UnknownAppleDevice +import eu.darken.capod.pods.core.apple.misc.UnknownAppleSnapshotBle import eu.darken.capod.pods.core.apple.protocol.ContinuityProtocol import eu.darken.capod.pods.core.apple.protocol.ProximityMessage import eu.darken.capod.pods.core.apple.protocol.ProximityPairing @@ -28,7 +28,7 @@ class AppleFactory @Inject constructor( private val proximityPairingDecoder: ProximityPairing.Decoder, private val proximityMessageDecrypter: ProximityMessage.Decrypter, private val podFactories: @JvmSuppressWildcards Set, - private val unknownAppleFactory: UnknownAppleDevice.Factory, + private val unknownAppleFactory: UnknownAppleSnapshotBle.Factory, private val rpaChecker: RPAChecker, private val profilesRepo: DeviceProfilesRepo, ) { @@ -60,7 +60,7 @@ class AppleFactory @Inject constructor( return proximityMessage } - suspend fun create(scanResult: BleScanResult): PodDevice? = lock.withLock { + suspend fun create(scanResult: BleScanResult): BlePodSnapshot? = lock.withLock { val proximityMessage = getMessage(scanResult) ?: return@withLock null val factory = podFactories.firstOrNull { it.isResponsible(proximityMessage) } ?: unknownAppleFactory diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ApplePods.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ApplePods.kt index c333f624..c07bea2e 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ApplePods.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ApplePods.kt @@ -2,11 +2,11 @@ package eu.darken.capod.pods.core.apple import eu.darken.capod.common.lowerNibble import eu.darken.capod.common.upperNibble -import eu.darken.capod.profiles.core.AppleDeviceProfile -import eu.darken.capod.pods.core.PodDevice +import eu.darken.capod.pods.core.BlePodSnapshot import eu.darken.capod.pods.core.apple.protocol.ProximityPayload +import eu.darken.capod.profiles.core.AppleDeviceProfile -interface ApplePods : PodDevice { +interface ApplePods : BlePodSnapshot { val payload: ProximityPayload @@ -59,7 +59,7 @@ interface ApplePods : PodDevice { data class AppleMeta( val isIRKMatch: Boolean = false, override val profile: AppleDeviceProfile? = null, - ) : PodDevice.Meta + ) : BlePodSnapshot.Meta override val meta: AppleMeta } \ No newline at end of file diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/DualApplePods.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/DualApplePods.kt index 14931dc6..4d08a686 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/DualApplePods.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/DualApplePods.kt @@ -4,14 +4,14 @@ import eu.darken.capod.common.debug.logging.log import eu.darken.capod.common.isBitSet import eu.darken.capod.common.lowerNibble import eu.darken.capod.common.upperNibble -import eu.darken.capod.pods.core.DualPodDevice -import eu.darken.capod.pods.core.DualPodDevice.Pod +import eu.darken.capod.pods.core.DualBlePodSnapshot +import eu.darken.capod.pods.core.DualBlePodSnapshot.Pod import eu.darken.capod.pods.core.HasCase import eu.darken.capod.pods.core.HasChargeDetectionDual import eu.darken.capod.pods.core.HasDualMicrophone import eu.darken.capod.pods.core.HasEarDetectionDual -interface DualApplePods : ApplePods, HasChargeDetectionDual, DualPodDevice, HasEarDetectionDual, HasCase, +interface DualApplePods : ApplePods, HasChargeDetectionDual, DualBlePodSnapshot, HasEarDetectionDual, HasCase, HasDualMicrophone, HasAppleColor { val primaryPod: Pod diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/SingleApplePods.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/SingleApplePods.kt index d1f08dcb..32f9e1d2 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/SingleApplePods.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/SingleApplePods.kt @@ -2,12 +2,12 @@ package eu.darken.capod.pods.core.apple import eu.darken.capod.common.debug.logging.log import eu.darken.capod.common.lowerNibble -import eu.darken.capod.pods.core.SinglePodDevice +import eu.darken.capod.pods.core.SingleBlePodSnapshot /** * Devices that only present a single charge level, e.g. most Beats devices */ -interface SingleApplePods : ApplePods, SinglePodDevice, HasAppleColor { +interface SingleApplePods : ApplePods, SingleBlePodSnapshot, HasAppleColor { override val batteryHeadsetPercent: Float? get() { diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen1.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen1.kt index 8a77290c..48da8ca4 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen1.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen1.kt @@ -2,7 +2,7 @@ package eu.darken.capod.pods.core.apple.airpods 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.BlePodSnapshot import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -15,14 +15,14 @@ import java.time.Instant import javax.inject.Inject data class AirPodsGen1( - override val identifier: PodDevice.Id = PodDevice.Id(), + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val payload: ProximityPayload, override val meta: ApplePods.AppleMeta, - override val reliability: Float = PodDevice.BASE_CONFIDENCE, + override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, private val cachedCaseState: DualApplePods.LidState? = null diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen2.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen2.kt index 5d303f4a..c7ebd194 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen2.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen2.kt @@ -2,7 +2,7 @@ package eu.darken.capod.pods.core.apple.airpods 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.BlePodSnapshot import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -15,14 +15,14 @@ import java.time.Instant import javax.inject.Inject data class AirPodsGen2( - override val identifier: PodDevice.Id = PodDevice.Id(), + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val payload: ProximityPayload, override val meta: ApplePods.AppleMeta, - override val reliability: Float = PodDevice.BASE_CONFIDENCE, + override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, private val cachedCaseState: DualApplePods.LidState? = null diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen3.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen3.kt index c28fac74..91fc0897 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen3.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen3.kt @@ -4,7 +4,7 @@ import androidx.annotation.DrawableRes import eu.darken.capod.R 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.BlePodSnapshot import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -17,14 +17,14 @@ import java.time.Instant import javax.inject.Inject data class AirPodsGen3( - override val identifier: PodDevice.Id = PodDevice.Id(), + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val payload: ProximityPayload, override val meta: ApplePods.AppleMeta, - override val reliability: Float = PodDevice.BASE_CONFIDENCE, + override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, private val cachedCaseState: DualApplePods.LidState? = null diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4.kt index 071e1b0b..363a7e4c 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4.kt @@ -4,7 +4,7 @@ import androidx.annotation.DrawableRes import eu.darken.capod.R 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.BlePodSnapshot import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -17,14 +17,14 @@ import java.time.Instant import javax.inject.Inject data class AirPodsGen4( - override val identifier: PodDevice.Id = PodDevice.Id(), + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val payload: ProximityPayload, override val meta: ApplePods.AppleMeta, - override val reliability: Float = PodDevice.BASE_CONFIDENCE, + override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, private val cachedCaseState: DualApplePods.LidState? = null diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4Anc.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4Anc.kt index eaa6a37c..4bcd6ce9 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4Anc.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4Anc.kt @@ -4,7 +4,7 @@ import androidx.annotation.DrawableRes import eu.darken.capod.R 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.BlePodSnapshot import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -17,14 +17,14 @@ import java.time.Instant import javax.inject.Inject data class AirPodsGen4Anc( - override val identifier: PodDevice.Id = PodDevice.Id(), + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val payload: ProximityPayload, override val meta: ApplePods.AppleMeta, - override val reliability: Float = PodDevice.BASE_CONFIDENCE, + override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, private val cachedCaseState: DualApplePods.LidState? = null diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMax.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMax.kt index 1f6f9eb9..f411f275 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMax.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMax.kt @@ -3,9 +3,9 @@ package eu.darken.capod.pods.core.apple.airpods import eu.darken.capod.common.bluetooth.BleScanResult import eu.darken.capod.common.debug.logging.logTag import eu.darken.capod.common.isBitSet +import eu.darken.capod.pods.core.BlePodSnapshot 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.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -19,14 +19,14 @@ import java.time.Instant import javax.inject.Inject data class AirPodsMax( - override val identifier: PodDevice.Id = PodDevice.Id(), + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val payload: ProximityPayload, override val meta: ApplePods.AppleMeta, - override val reliability: Float = PodDevice.BASE_CONFIDENCE, + override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE, private val rssiAverage: Int? = null, ) : SingleApplePods, HasEarDetection, HasChargeDetection, HasAppleColor { diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMaxUsbc.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMaxUsbc.kt index 65142528..d0ad3b9e 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMaxUsbc.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMaxUsbc.kt @@ -3,9 +3,9 @@ package eu.darken.capod.pods.core.apple.airpods import eu.darken.capod.common.bluetooth.BleScanResult import eu.darken.capod.common.debug.logging.logTag import eu.darken.capod.common.isBitSet +import eu.darken.capod.pods.core.BlePodSnapshot 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.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -19,14 +19,14 @@ import java.time.Instant import javax.inject.Inject data class AirPodsMaxUsbc( - override val identifier: PodDevice.Id = PodDevice.Id(), + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val payload: ProximityPayload, override val meta: ApplePods.AppleMeta, - override val reliability: Float = PodDevice.BASE_CONFIDENCE, + override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE, private val rssiAverage: Int? = null, ) : SingleApplePods, HasEarDetection, HasChargeDetection, HasAppleColor { diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro.kt index 58413811..0733bb18 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro.kt @@ -4,7 +4,7 @@ import androidx.annotation.DrawableRes import eu.darken.capod.R 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.BlePodSnapshot import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -18,14 +18,14 @@ import java.time.Instant import javax.inject.Inject data class AirPodsPro( - override val identifier: PodDevice.Id = PodDevice.Id(), + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val payload: ProximityPayload, override val meta: ApplePods.AppleMeta, - override val reliability: Float = PodDevice.BASE_CONFIDENCE, + override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, private val cachedCaseState: LidState? = null diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2.kt index 1ecde044..0d41974b 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2.kt @@ -4,7 +4,7 @@ import androidx.annotation.DrawableRes import eu.darken.capod.R 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.BlePodSnapshot import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -18,14 +18,14 @@ import java.time.Instant import javax.inject.Inject data class AirPodsPro2( - override val identifier: PodDevice.Id = PodDevice.Id(), + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val payload: ProximityPayload, override val meta: ApplePods.AppleMeta, - override val reliability: Float = PodDevice.BASE_CONFIDENCE, + override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, private val cachedCaseState: LidState? = null diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2Usbc.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2Usbc.kt index 4bf9b195..b8987f3c 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2Usbc.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2Usbc.kt @@ -4,7 +4,7 @@ import androidx.annotation.DrawableRes import eu.darken.capod.R 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.BlePodSnapshot import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -18,14 +18,14 @@ import java.time.Instant import javax.inject.Inject data class AirPodsPro2Usbc( - override val identifier: PodDevice.Id = PodDevice.Id(), + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val payload: ProximityPayload, override val meta: ApplePods.AppleMeta, - override val reliability: Float = PodDevice.BASE_CONFIDENCE, + override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, private val cachedCaseState: LidState? = null diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro3.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro3.kt index c6351413..cb857cf0 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro3.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro3.kt @@ -4,7 +4,7 @@ import androidx.annotation.DrawableRes import eu.darken.capod.R 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.BlePodSnapshot import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -18,13 +18,13 @@ import java.time.Instant import javax.inject.Inject data class AirPodsPro3( - override val identifier: PodDevice.Id = PodDevice.Id(), + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val payload: ProximityPayload, - override val reliability: Float = PodDevice.BASE_CONFIDENCE, + override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE, override val meta: ApplePods.AppleMeta, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFitPro.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFitPro.kt index f46d0383..825476ae 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFitPro.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFitPro.kt @@ -4,7 +4,7 @@ import androidx.annotation.DrawableRes import eu.darken.capod.R 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.BlePodSnapshot import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -17,14 +17,14 @@ import java.time.Instant import javax.inject.Inject data class BeatsFitPro( - override val identifier: PodDevice.Id = PodDevice.Id(), + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val payload: ProximityPayload, override val meta: ApplePods.AppleMeta, - override val reliability: Float = PodDevice.BASE_CONFIDENCE, + override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, private val cachedCaseState: DualApplePods.LidState? = null diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFlex.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFlex.kt index 0fbc9ce3..13f32374 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFlex.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFlex.kt @@ -2,7 +2,7 @@ package eu.darken.capod.pods.core.apple.beats 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.BlePodSnapshot import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -15,14 +15,14 @@ import java.time.Instant import javax.inject.Inject data class BeatsFlex( - override val identifier: PodDevice.Id = PodDevice.Id(), + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val payload: ProximityPayload, override val meta: ApplePods.AppleMeta, - override val reliability: Float = PodDevice.BASE_CONFIDENCE, + override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE, private val rssiAverage: Int? = null, ) : SingleApplePods { diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsSolo3.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsSolo3.kt index 00445ca2..0ca015a6 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsSolo3.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsSolo3.kt @@ -2,7 +2,7 @@ package eu.darken.capod.pods.core.apple.beats 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.BlePodSnapshot import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -15,14 +15,14 @@ import java.time.Instant import javax.inject.Inject data class BeatsSolo3( - override val identifier: PodDevice.Id = PodDevice.Id(), + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val payload: ProximityPayload, override val meta: ApplePods.AppleMeta, - override val reliability: Float = PodDevice.BASE_CONFIDENCE, + override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE, private val rssiAverage: Int? = null, ) : SingleApplePods { diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsStudio3.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsStudio3.kt index 73ed96f2..5368ed5d 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsStudio3.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsStudio3.kt @@ -2,7 +2,7 @@ package eu.darken.capod.pods.core.apple.beats 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.BlePodSnapshot import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -15,14 +15,14 @@ import java.time.Instant import javax.inject.Inject data class BeatsStudio3( - override val identifier: PodDevice.Id = PodDevice.Id(), + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val payload: ProximityPayload, override val meta: ApplePods.AppleMeta, - override val reliability: Float = PodDevice.BASE_CONFIDENCE, + override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE, private val rssiAverage: Int? = null, ) : SingleApplePods { diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsX.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsX.kt index 56a6dc42..10152d91 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsX.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsX.kt @@ -2,7 +2,7 @@ package eu.darken.capod.pods.core.apple.beats 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.BlePodSnapshot import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -15,14 +15,14 @@ import java.time.Instant import javax.inject.Inject data class BeatsX( - override val identifier: PodDevice.Id = PodDevice.Id(), + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val payload: ProximityPayload, override val meta: ApplePods.AppleMeta, - override val reliability: Float = PodDevice.BASE_CONFIDENCE, + override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE, private val rssiAverage: Int? = null, ) : SingleApplePods { diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats3.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats3.kt index d848b152..a5b6f9a2 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats3.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats3.kt @@ -2,7 +2,7 @@ package eu.darken.capod.pods.core.apple.beats 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.BlePodSnapshot import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -15,14 +15,14 @@ import java.time.Instant import javax.inject.Inject data class PowerBeats3( - override val identifier: PodDevice.Id = PodDevice.Id(), + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val payload: ProximityPayload, override val meta: ApplePods.AppleMeta, - override val reliability: Float = PodDevice.BASE_CONFIDENCE, + override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE, private val rssiAverage: Int? = null, ) : SingleApplePods { diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats4.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats4.kt index 15836dc6..a9192df0 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats4.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats4.kt @@ -2,7 +2,7 @@ package eu.darken.capod.pods.core.apple.beats 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.BlePodSnapshot import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -16,14 +16,14 @@ import java.time.Instant import javax.inject.Inject data class PowerBeats4( - override val identifier: PodDevice.Id = PodDevice.Id(), + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val payload: ProximityPayload, override val meta: ApplePods.AppleMeta, - override val reliability: Float = PodDevice.BASE_CONFIDENCE, + override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE, private val rssiAverage: Int? = null, ) : SingleApplePods, HasStateDetectionAirPods { diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro.kt index 2d63abfa..14a83c01 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro.kt @@ -4,7 +4,7 @@ import androidx.annotation.DrawableRes import eu.darken.capod.R 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.BlePodSnapshot import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -17,14 +17,14 @@ import java.time.Instant import javax.inject.Inject data class PowerBeatsPro( - override val identifier: PodDevice.Id = PodDevice.Id(), + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val payload: ProximityPayload, override val meta: ApplePods.AppleMeta, - override val reliability: Float = PodDevice.BASE_CONFIDENCE, + override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, private val cachedCaseState: DualApplePods.LidState? = null diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro2.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro2.kt index 2346e8c6..1b9acd38 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro2.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro2.kt @@ -4,7 +4,7 @@ import androidx.annotation.DrawableRes import eu.darken.capod.R 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.BlePodSnapshot import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -17,14 +17,14 @@ import java.time.Instant import javax.inject.Inject data class PowerBeatsPro2( - override val identifier: PodDevice.Id = PodDevice.Id(), + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val payload: ProximityPayload, override val meta: ApplePods.AppleMeta, - override val reliability: Float = PodDevice.BASE_CONFIDENCE, + override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, private val cachedCaseState: DualApplePods.LidState? = null diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/history/KnownDevice.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/history/KnownDevice.kt index 70e4cff3..d22fb494 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/history/KnownDevice.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/history/KnownDevice.kt @@ -2,7 +2,7 @@ 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.BlePodSnapshot import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Duration @@ -10,7 +10,7 @@ import java.time.Instant import kotlin.math.max data class KnownDevice( - val id: PodDevice.Id, + val id: BlePodSnapshot.Id, val seenFirstAt: Instant, val seenCounter: Int, val history: List, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/history/PodHistoryRepo.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/history/PodHistoryRepo.kt index c84deb73..fd5004f9 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/history/PodHistoryRepo.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/history/PodHistoryRepo.kt @@ -7,8 +7,8 @@ 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.pods.core.BlePodSnapshot import eu.darken.capod.pods.core.HasCase -import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.DualApplePods @@ -27,7 +27,7 @@ class PodHistoryRepo @Inject constructor( private val rpaChecker: RPAChecker, ) { - private val knownDevices = mutableMapOf() + private val knownDevices = mutableMapOf() data class Identifier( val device: UShort, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen1.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen1.kt index 6f908558..e9c51278 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen1.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen1.kt @@ -2,10 +2,10 @@ package eu.darken.capod.pods.core.apple.misc import eu.darken.capod.common.bluetooth.BleScanResult import eu.darken.capod.common.debug.logging.logTag +import eu.darken.capod.pods.core.BlePodSnapshot import eu.darken.capod.pods.core.HasCase import eu.darken.capod.pods.core.HasChargeDetectionDual import eu.darken.capod.pods.core.HasEarDetectionDual -import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -22,14 +22,14 @@ import javax.inject.Inject * Marketed as TWS i99999 */ data class FakeAirPodsGen1( - override val identifier: PodDevice.Id = PodDevice.Id(), + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val payload: ProximityPayload, override val meta: ApplePods.AppleMeta, - override val reliability: Float = PodDevice.BASE_CONFIDENCE, + override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, ) : DualApplePods, HasEarDetectionDual, HasChargeDetectionDual, HasCase { diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen2.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen2.kt index 7c930344..dadc3a4c 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen2.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen2.kt @@ -2,10 +2,10 @@ package eu.darken.capod.pods.core.apple.misc import eu.darken.capod.common.bluetooth.BleScanResult import eu.darken.capod.common.debug.logging.logTag +import eu.darken.capod.pods.core.BlePodSnapshot import eu.darken.capod.pods.core.HasCase import eu.darken.capod.pods.core.HasChargeDetectionDual import eu.darken.capod.pods.core.HasEarDetectionDual -import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -20,14 +20,14 @@ import javax.inject.Inject * Similar data structure but a lot of placeholder values or hardcoded values */ data class FakeAirPodsGen2( - override val identifier: PodDevice.Id = PodDevice.Id(), + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val payload: ProximityPayload, override val meta: ApplePods.AppleMeta, - override val reliability: Float = PodDevice.BASE_CONFIDENCE, + override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, ) : DualApplePods, HasEarDetectionDual, HasChargeDetectionDual, HasCase { diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen3.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen3.kt index d1ff6a3f..fbbb3c0e 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen3.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen3.kt @@ -4,10 +4,10 @@ import androidx.annotation.DrawableRes import eu.darken.capod.R import eu.darken.capod.common.bluetooth.BleScanResult import eu.darken.capod.common.debug.logging.logTag +import eu.darken.capod.pods.core.BlePodSnapshot import eu.darken.capod.pods.core.HasCase import eu.darken.capod.pods.core.HasChargeDetectionDual import eu.darken.capod.pods.core.HasEarDetectionDual -import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -24,14 +24,14 @@ import javax.inject.Inject * https://discord.com/channels/548521543039189022/927235844127993866/1054404630118924308 */ data class FakeAirPodsGen3( - override val identifier: PodDevice.Id = PodDevice.Id(), + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val payload: ProximityPayload, override val meta: ApplePods.AppleMeta, - override val reliability: Float = PodDevice.BASE_CONFIDENCE, + override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, ) : DualApplePods, HasEarDetectionDual, HasChargeDetectionDual, HasCase { diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro.kt index 80e17608..58f72366 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro.kt @@ -4,10 +4,10 @@ import androidx.annotation.DrawableRes import eu.darken.capod.R import eu.darken.capod.common.bluetooth.BleScanResult import eu.darken.capod.common.debug.logging.logTag +import eu.darken.capod.pods.core.BlePodSnapshot import eu.darken.capod.pods.core.HasCase import eu.darken.capod.pods.core.HasChargeDetectionDual import eu.darken.capod.pods.core.HasEarDetectionDual -import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -24,14 +24,14 @@ import javax.inject.Inject * https://discord.com/channels/548521543039189022/927235844127993866/962068944196358234 */ data class FakeAirPodsPro( - override val identifier: PodDevice.Id = PodDevice.Id(), + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val payload: ProximityPayload, override val meta: ApplePods.AppleMeta, - override val reliability: Float = PodDevice.BASE_CONFIDENCE, + override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, ) : DualApplePods, HasChargeDetectionDual, HasEarDetectionDual, HasCase { diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro2.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro2.kt index 838249d3..e1d2763a 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro2.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro2.kt @@ -4,11 +4,11 @@ import androidx.annotation.DrawableRes import eu.darken.capod.R import eu.darken.capod.common.bluetooth.BleScanResult import eu.darken.capod.common.debug.logging.logTag -import eu.darken.capod.pods.core.DualPodDevice +import eu.darken.capod.pods.core.BlePodSnapshot +import eu.darken.capod.pods.core.DualBlePodSnapshot import eu.darken.capod.pods.core.HasCase import eu.darken.capod.pods.core.HasChargeDetectionDual import eu.darken.capod.pods.core.HasEarDetectionDual -import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -25,18 +25,18 @@ import javax.inject.Inject * https://discord.com/channels/548521543039189022/927235844127993866/1060911213539774504 */ data class FakeAirPodsPro2( - override val identifier: PodDevice.Id = PodDevice.Id(), + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val payload: ProximityPayload, override val meta: ApplePods.AppleMeta, - override val reliability: Float = PodDevice.BASE_CONFIDENCE, + override val reliability: Float = BlePodSnapshot.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, private val cachedCaseState: LidState? = null, -) : DualApplePods, HasChargeDetectionDual, DualPodDevice, HasEarDetectionDual, HasCase { +) : DualApplePods, HasChargeDetectionDual, DualBlePodSnapshot, HasEarDetectionDual, HasCase { override val model: PodModel = PodModel.FAKE_AIRPODS_PRO2 diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/misc/UnknownAppleDevice.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/misc/UnknownAppleSnapshotBle.kt similarity index 89% rename from app/src/main/java/eu/darken/capod/pods/core/apple/misc/UnknownAppleDevice.kt rename to app/src/main/java/eu/darken/capod/pods/core/apple/misc/UnknownAppleSnapshotBle.kt index 1e251521..203c4323 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/misc/UnknownAppleDevice.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/misc/UnknownAppleSnapshotBle.kt @@ -4,7 +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.pods.core.PodDevice +import eu.darken.capod.pods.core.BlePodSnapshot import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.ApplePods import eu.darken.capod.pods.core.apple.ApplePodsFactory @@ -14,8 +14,8 @@ import eu.darken.capod.pods.core.apple.protocol.ProximityPayload import java.time.Instant import javax.inject.Inject -data class UnknownAppleDevice( - override val identifier: PodDevice.Id = PodDevice.Id(), +data class UnknownAppleSnapshotBle( + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, @@ -43,7 +43,7 @@ data class UnknownAppleDevice( payload: ProximityPayload, meta: ApplePods.AppleMeta, ): ApplePods { - var basic = UnknownAppleDevice(scanResult = scanResult, payload = payload, meta = meta) + var basic = UnknownAppleSnapshotBle(scanResult = scanResult, payload = payload, meta = meta) val result = repo.search(basic) if (result != null) basic = basic.copy(identifier = result.id) diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/protocol/RPAChecker.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/protocol/RPAChecker.kt index fe808720..72573027 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/protocol/RPAChecker.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/protocol/RPAChecker.kt @@ -47,6 +47,6 @@ class RPAChecker @Inject constructor() { } companion object { - private val TAG = logTag("Monitor", "PodMonitor", "RPAChecker") + private val TAG = logTag("Monitor", "BlePodMonitor", "RPAChecker") } } \ No newline at end of file diff --git a/app/src/main/java/eu/darken/capod/pods/core/unknown/UnknownDeviceFactory.kt b/app/src/main/java/eu/darken/capod/pods/core/unknown/UnknownDeviceFactory.kt index 7fae3918..f58ae420 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/unknown/UnknownDeviceFactory.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/unknown/UnknownDeviceFactory.kt @@ -4,7 +4,7 @@ import eu.darken.capod.common.bluetooth.BleScanResult import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE import eu.darken.capod.common.debug.logging.log import eu.darken.capod.common.debug.logging.logTag -import eu.darken.capod.pods.core.PodDevice +import eu.darken.capod.pods.core.BlePodSnapshot import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import java.time.Duration @@ -17,9 +17,9 @@ class UnknownDeviceFactory @Inject constructor() { private val lock = Mutex() - suspend fun create(scanResult: BleScanResult): PodDevice? = lock.withLock { - var basic = UnknownDevice( - identifier = PodDevice.Id(), + suspend fun create(scanResult: BleScanResult): BlePodSnapshot = lock.withLock { + var basic = UnknownSnapshotBle( + identifier = BlePodSnapshot.Id(), scanResult = scanResult, ) val result = searchHistory(basic) @@ -40,10 +40,10 @@ class UnknownDeviceFactory @Inject constructor() { } data class KnownDevice( - val id: PodDevice.Id, + val id: BlePodSnapshot.Id, val seenFirstAt: Instant, val seenCounter: Int, - val history: List + val history: List ) { val lastAddress: String @@ -74,9 +74,9 @@ class UnknownDeviceFactory @Inject constructor() { } } - private val knownDevices = mutableMapOf() + private val knownDevices = mutableMapOf() - private fun searchHistory(current: PodDevice): KnownDevice? { + private fun searchHistory(current: BlePodSnapshot): KnownDevice? { val scanResult = current.scanResult knownDevices.values.toList().forEach { knownDevice -> @@ -104,7 +104,7 @@ class UnknownDeviceFactory @Inject constructor() { return recognizedDevice } - private fun updateHistory(device: PodDevice) { + private fun updateHistory(device: BlePodSnapshot) { val existing = knownDevices[device.identifier] knownDevices[device.identifier] = when { @@ -114,6 +114,7 @@ class UnknownDeviceFactory @Inject constructor() { history = existing.history.plus(device) ) } + else -> { log(TAG) { "searchHistory1: Creating new history for $device" } KnownDevice( diff --git a/app/src/main/java/eu/darken/capod/pods/core/unknown/UnknownDevice.kt b/app/src/main/java/eu/darken/capod/pods/core/unknown/UnknownSnapshotBle.kt similarity index 83% rename from app/src/main/java/eu/darken/capod/pods/core/unknown/UnknownDevice.kt rename to app/src/main/java/eu/darken/capod/pods/core/unknown/UnknownSnapshotBle.kt index 721f5f43..92de2c75 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/unknown/UnknownDevice.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/unknown/UnknownSnapshotBle.kt @@ -4,13 +4,13 @@ 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.pods.core.PodDevice +import eu.darken.capod.pods.core.BlePodSnapshot import eu.darken.capod.pods.core.PodModel import eu.darken.capod.profiles.core.DeviceProfile import java.time.Instant -data class UnknownDevice( - override val identifier: PodDevice.Id = PodDevice.Id(), +data class UnknownSnapshotBle( + override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(), override val seenLastAt: Instant = Instant.now(), override val seenFirstAt: Instant = Instant.now(), override val seenCounter: Int = 1, @@ -18,7 +18,7 @@ data class UnknownDevice( override val reliability: Float = 0f, override val meta: Meta = Meta(), private val rssiAverage: Int? = null, -) : PodDevice { +) : BlePodSnapshot { override val model: PodModel = PodModel.UNKNOWN @@ -29,7 +29,7 @@ data class UnknownDevice( data class Meta( override val profile: DeviceProfile? = null - ) : PodDevice.Meta + ) : BlePodSnapshot.Meta companion object { private val TAG = logTag("PodDevice", "Unknown") diff --git a/app/src/main/java/eu/darken/capod/reaction/core/playpause/PlayPause.kt b/app/src/main/java/eu/darken/capod/reaction/core/playpause/PlayPause.kt index 66dd17e8..1a2de6b1 100644 --- a/app/src/main/java/eu/darken/capod/reaction/core/playpause/PlayPause.kt +++ b/app/src/main/java/eu/darken/capod/reaction/core/playpause/PlayPause.kt @@ -2,6 +2,7 @@ package eu.darken.capod.reaction.core.playpause import eu.darken.capod.common.MediaControl import eu.darken.capod.common.bluetooth.BluetoothManager2 +import eu.darken.capod.common.datastore.valueBlocking 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 @@ -9,7 +10,6 @@ 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.DeviceMonitor -import eu.darken.capod.monitor.core.MonitoredDevice import eu.darken.capod.monitor.core.primaryDevice import eu.darken.capod.reaction.core.ReactionSettings import kotlinx.coroutines.flow.combine @@ -20,7 +20,6 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.onEach import javax.inject.Inject import javax.inject.Singleton -import eu.darken.capod.common.datastore.valueBlocking @Singleton class PlayPause @Inject constructor( @@ -63,7 +62,7 @@ class PlayPause @Inject constructor( when { previous!!.hasEarDetection && previous.hasDualPods && - current!!.hasEarDetection && current.hasDualPods -> { + current!!.hasEarDetection && current.hasDualPods -> { // Dual pod devices (AirPods, AirPods Pro, etc.) log(TAG, VERBOSE) { "Dual-pod device: left=${current.isLeftInEar}, right=${current.isRightInEar}" diff --git a/app/src/main/java/eu/darken/capod/reaction/core/popup/PopUpReaction.kt b/app/src/main/java/eu/darken/capod/reaction/core/popup/PopUpReaction.kt index 6ec142dc..c65a531b 100644 --- a/app/src/main/java/eu/darken/capod/reaction/core/popup/PopUpReaction.kt +++ b/app/src/main/java/eu/darken/capod/reaction/core/popup/PopUpReaction.kt @@ -2,15 +2,13 @@ package eu.darken.capod.reaction.core.popup import eu.darken.capod.common.bluetooth.BluetoothAddress import eu.darken.capod.common.bluetooth.BluetoothManager2 -import eu.darken.capod.common.debug.logging.Logging.Priority.INFO 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.flow.setupCommonEventHandlers import eu.darken.capod.common.flow.withPrevious import eu.darken.capod.monitor.core.DeviceMonitor -import eu.darken.capod.monitor.core.MonitoredDevice +import eu.darken.capod.monitor.core.PodDevice import eu.darken.capod.monitor.core.primaryDevice import eu.darken.capod.pods.core.apple.DualApplePods import eu.darken.capod.reaction.core.ReactionSettings @@ -55,7 +53,7 @@ class PopUpReaction @Inject constructor( log(TAG, VERBOSE) { "previous-id=${previous?.identifier}, current-id=${current.identifier}" } val isSameDeviceOrProfile = previous?.identifier == current.identifier || - (previous?.meta?.profile?.id != null && previous.meta?.profile?.id == current.meta?.profile?.id) + (previous?.meta?.profile?.id != null && previous.meta?.profile?.id == current.meta?.profile?.id) val isSameDeviceWithCaseNowOpen = isSameDeviceOrProfile && previous?.caseLidState != current.caseLidState val isNewDeviceWithJustOpenedCase = !isSameDeviceOrProfile && previous?.caseLidState != current.caseLidState @@ -67,7 +65,7 @@ class PopUpReaction @Inject constructor( throttleCasePopUps(current) } - private fun throttleCasePopUps(current: MonitoredDevice): Event? { + private fun throttleCasePopUps(current: PodDevice): Event? { val cooldownKey = current.meta?.profile?.id ?: current.identifier.toString() val now = Instant.now() val lastShown = caseCoolDowns[cooldownKey] @@ -89,12 +87,14 @@ class PopUpReaction @Inject constructor( caseCoolDowns[cooldownKey] = now Event.PopupShow(device = current) } + decision.shouldHide -> { if (!decision.shouldResetCooldown) { caseCoolDowns[cooldownKey] = now } Event.PopupHide() } + else -> null } } @@ -169,7 +169,7 @@ class PopUpReaction @Inject constructor( sealed class Event { data class PopupShow( val eventAt: Instant = Instant.now(), - val device: MonitoredDevice, + val device: PodDevice, ) : Event() data class PopupHide( @@ -210,12 +210,14 @@ class PopUpReaction @Inject constructor( ) } } + DualApplePods.LidState.CLOSED -> CasePopUpDecision( shouldShow = false, shouldHide = true, shouldResetCooldown = true, reason = "Lid CLOSED, resetting cooldown", ) + else -> CasePopUpDecision( shouldShow = false, shouldHide = true, diff --git a/app/src/main/java/eu/darken/capod/reaction/ui/popup/PopUpContent.kt b/app/src/main/java/eu/darken/capod/reaction/ui/popup/PopUpContent.kt index 5032e0ea..f564d87e 100644 --- a/app/src/main/java/eu/darken/capod/reaction/ui/popup/PopUpContent.kt +++ b/app/src/main/java/eu/darken/capod/reaction/ui/popup/PopUpContent.kt @@ -34,7 +34,7 @@ import eu.darken.capod.R import eu.darken.capod.common.compose.Preview2 import eu.darken.capod.common.compose.PreviewWrapper import eu.darken.capod.common.compose.preview.MockPodDataProvider -import eu.darken.capod.monitor.core.MonitoredDevice +import eu.darken.capod.monitor.core.PodDevice import eu.darken.capod.monitor.core.getSignalQuality import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.formatBatteryPercent @@ -44,7 +44,7 @@ import eu.darken.capod.pods.core.toBatteryOrNull @Composable fun PopUpContent( - device: MonitoredDevice, + device: PodDevice, onClose: () -> Unit, modifier: Modifier = Modifier, ) { @@ -117,7 +117,7 @@ fun PopUpContent( } @Composable -private fun DualPodContent(device: MonitoredDevice) { +private fun DualPodContent(device: PodDevice) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly, @@ -148,7 +148,7 @@ private fun DualPodContent(device: MonitoredDevice) { } @Composable -private fun SinglePodContent(device: MonitoredDevice) { +private fun SinglePodContent(device: PodDevice) { BatteryColumn( iconRes = device.iconRes, batteryPercent = device.batteryHeadset.toBatteryFloat(), diff --git a/app/src/main/java/eu/darken/capod/reaction/ui/popup/PopUpWindow.kt b/app/src/main/java/eu/darken/capod/reaction/ui/popup/PopUpWindow.kt index a810cf0a..80ed16db 100644 --- a/app/src/main/java/eu/darken/capod/reaction/ui/popup/PopUpWindow.kt +++ b/app/src/main/java/eu/darken/capod/reaction/ui/popup/PopUpWindow.kt @@ -23,7 +23,7 @@ import eu.darken.capod.common.debug.logging.logTag import eu.darken.capod.common.theming.CapodTheme import eu.darken.capod.main.core.GeneralSettings import eu.darken.capod.main.core.currentThemeState -import eu.darken.capod.monitor.core.MonitoredDevice +import eu.darken.capod.monitor.core.PodDevice import javax.inject.Inject import javax.inject.Singleton @@ -50,9 +50,9 @@ class PopUpWindow @Inject constructor( private var composeView: ComposeView? = null private var lifecycleOwner: OverlayLifecycleOwner? = null - private var deviceState: MutableState? = null + private var deviceState: MutableState? = null - fun show(device: MonitoredDevice) { + fun show(device: PodDevice) { try { log(TAG) { "open()" } @@ -64,7 +64,7 @@ class PopUpWindow @Inject constructor( teardown() - val state = mutableStateOf(device) + val state = mutableStateOf(device) deviceState = state val owner = OverlayLifecycleOwner() diff --git a/app/src/main/java/eu/darken/capod/troubleshooter/ui/TroubleShooterViewModel.kt b/app/src/main/java/eu/darken/capod/troubleshooter/ui/TroubleShooterViewModel.kt index ca46ed70..70707bb3 100644 --- a/app/src/main/java/eu/darken/capod/troubleshooter/ui/TroubleShooterViewModel.kt +++ b/app/src/main/java/eu/darken/capod/troubleshooter/ui/TroubleShooterViewModel.kt @@ -6,18 +6,18 @@ import dagger.hilt.android.qualifiers.ApplicationContext import eu.darken.capod.R import eu.darken.capod.common.bluetooth.ScannerMode import eu.darken.capod.common.coroutine.DispatcherProvider +import eu.darken.capod.common.datastore.valueBlocking import eu.darken.capod.common.debug.DebugSettings import eu.darken.capod.common.debug.logging.Logging.Priority.INFO import eu.darken.capod.common.debug.logging.log import eu.darken.capod.common.debug.logging.logTag - import eu.darken.capod.common.uix.ViewModel4 import eu.darken.capod.main.core.GeneralSettings +import eu.darken.capod.monitor.core.BlePodMonitor import eu.darken.capod.monitor.core.DeviceMonitor -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 eu.darken.capod.pods.core.BlePodSnapshot +import eu.darken.capod.pods.core.unknown.UnknownSnapshotBle import eu.darken.capod.profiles.core.AppleDeviceProfile import eu.darken.capod.profiles.core.DeviceProfilesRepo import kotlinx.coroutines.flow.MutableStateFlow @@ -31,7 +31,6 @@ import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.flow.toList import kotlinx.coroutines.withTimeoutOrNull import javax.inject.Inject -import eu.darken.capod.common.datastore.valueBlocking @HiltViewModel class TroubleShooterViewModel @Inject constructor( @@ -39,7 +38,7 @@ class TroubleShooterViewModel @Inject constructor( private val dispatcherProvider: DispatcherProvider, private val generalSettings: GeneralSettings, private val profilesRepo: DeviceProfilesRepo, - private val podMonitor: PodMonitor, + private val blePodMonitor: BlePodMonitor, private val deviceMonitor: DeviceMonitor, private val debugSettings: DebugSettings, ) : ViewModel4(dispatcherProvider) { @@ -98,39 +97,40 @@ class TroubleShooterViewModel @Inject constructor( } } - val doScan: suspend (Boolean, Boolean, Boolean, Boolean) -> Collection = { hardwareFilteringDisabled, - hardwareBatchingDisabled, - indirectCallback, - unfiltered -> - val sb = StringBuilder("SCAN - Settings: ") - sb.append("hardwareFilteringDisabled=$hardwareFilteringDisabled, ") - sb.append("hardwareBatchingDisabled=$hardwareBatchingDisabled, ") - sb.append("indirectCallback=$indirectCallback, ") - sb.append("unfiltered=$unfiltered") - progress(sb.toString()) - generalSettings.isOffloadedFilteringDisabled.valueBlocking = hardwareFilteringDisabled - generalSettings.isOffloadedBatchingDisabled.valueBlocking = hardwareBatchingDisabled - generalSettings.useIndirectScanResultCallback.valueBlocking = indirectCallback - debugSettings.showUnfiltered.valueBlocking = unfiltered + val doScan: suspend (Boolean, Boolean, Boolean, Boolean) -> Collection = + { hardwareFilteringDisabled, + hardwareBatchingDisabled, + indirectCallback, + unfiltered -> + val sb = StringBuilder("SCAN - Settings: ") + sb.append("hardwareFilteringDisabled=$hardwareFilteringDisabled, ") + sb.append("hardwareBatchingDisabled=$hardwareBatchingDisabled, ") + sb.append("indirectCallback=$indirectCallback, ") + sb.append("unfiltered=$unfiltered") + progress(sb.toString()) + generalSettings.isOffloadedFilteringDisabled.valueBlocking = hardwareFilteringDisabled + generalSettings.isOffloadedBatchingDisabled.valueBlocking = hardwareBatchingDisabled + generalSettings.useIndirectScanResultCallback.valueBlocking = indirectCallback + debugSettings.showUnfiltered.valueBlocking = unfiltered - val start = System.currentTimeMillis() - val devices = withTimeoutOrNull(STEP_TIME) { - podMonitor.devices - .take(10) - .takeWhile { System.currentTimeMillis() - start < STEP_TIME - 1000 } - .toList() - .flatten() - .distinctBy { it.address } - } ?: emptyList() - log(TAG) { "SCAN: BLE Devices: $devices" } - if (devices.isNotEmpty()) { - progress("SCAN: Received data from ${devices.size} BLE devices") - devices - } else { - progress("SCAN: No data received") - devices + val start = System.currentTimeMillis() + val devices = withTimeoutOrNull(STEP_TIME) { + blePodMonitor.devices + .take(10) + .takeWhile { System.currentTimeMillis() - start < STEP_TIME - 1000 } + .toList() + .flatten() + .distinctBy { it.address } + } ?: emptyList() + log(TAG) { "SCAN: BLE Devices: $devices" } + if (devices.isNotEmpty()) { + progress("SCAN: Received data from ${devices.size} BLE devices") + devices + } else { + progress("SCAN: No data received") + devices + } } - } run { progress("Checking if we can receive BLE data at all.") @@ -158,14 +158,14 @@ class TroubleShooterViewModel @Inject constructor( run { progress("Checking for supported headphones.") - if (doScan(false, false, false, false).any { it !is UnknownDevice }) return@run - if (doScan(false, false, true, false).any { it !is UnknownDevice }) return@run - if (doScan(true, true, true, false).any { it !is UnknownDevice }) return@run - if (doScan(true, true, false, false).any { it !is UnknownDevice }) return@run - if (doScan(true, false, true, false).any { it !is UnknownDevice }) return@run - if (doScan(true, false, false, false).any { it !is UnknownDevice }) return@run - if (doScan(false, true, true, false).any { it !is UnknownDevice }) return@run - if (doScan(false, true, false, false).any { it !is UnknownDevice }) return@run + if (doScan(false, false, false, false).any { it !is UnknownSnapshotBle }) return@run + if (doScan(false, false, true, false).any { it !is UnknownSnapshotBle }) return@run + if (doScan(true, true, true, false).any { it !is UnknownSnapshotBle }) return@run + if (doScan(true, true, false, false).any { it !is UnknownSnapshotBle }) return@run + if (doScan(true, false, true, false).any { it !is UnknownSnapshotBle }) return@run + if (doScan(true, false, false, false).any { it !is UnknownSnapshotBle }) return@run + if (doScan(false, true, true, false).any { it !is UnknownSnapshotBle }) return@run + if (doScan(false, true, false, false).any { it !is UnknownSnapshotBle }) return@run failure("No compatible headphones found", BleState.Result.Failure.Type.HEADPHONES) @@ -196,7 +196,7 @@ class TroubleShooterViewModel @Inject constructor( val otherDevices = withTimeoutOrNull(STEP_TIME) { val start = System.currentTimeMillis() - podMonitor.devices + blePodMonitor.devices .take(10) .takeWhile { System.currentTimeMillis() - start < STEP_TIME - 1000 } .toList() @@ -215,7 +215,7 @@ class TroubleShooterViewModel @Inject constructor( progress("Creating profile for closest headphones.") val candidate = otherDevices - .filter { it !is UnknownDevice } + .filter { it !is UnknownSnapshotBle } .maxBy { it.signalQuality } log(TAG, INFO) { "Candidate is $candidate" } diff --git a/app/src/test/java/eu/darken/capod/main/ui/overview/OverviewViewModelTest.kt b/app/src/test/java/eu/darken/capod/main/ui/overview/OverviewViewModelTest.kt index 11a34fb3..2e67987a 100644 --- a/app/src/test/java/eu/darken/capod/main/ui/overview/OverviewViewModelTest.kt +++ b/app/src/test/java/eu/darken/capod/main/ui/overview/OverviewViewModelTest.kt @@ -9,9 +9,9 @@ import eu.darken.capod.main.core.GeneralSettings import eu.darken.capod.main.core.MonitorMode import eu.darken.capod.main.core.PermissionTool import eu.darken.capod.monitor.core.DeviceMonitor -import eu.darken.capod.monitor.core.MonitoredDevice +import eu.darken.capod.monitor.core.PodDevice import eu.darken.capod.monitor.core.worker.MonitorControl -import eu.darken.capod.pods.core.PodDevice +import eu.darken.capod.pods.core.BlePodSnapshot import eu.darken.capod.profiles.core.AppleDeviceProfile import eu.darken.capod.profiles.core.DeviceProfile import eu.darken.capod.profiles.core.DeviceProfilesRepo @@ -32,11 +32,11 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith import testhelpers.BaseTest import testhelpers.coroutine.TestDispatcherProvider import testhelpers.datastore.FakeDataStoreValue import testhelpers.livedata.InstantExecutorExtension -import org.junit.jupiter.api.extension.ExtendWith @OptIn(ExperimentalCoroutinesApi::class) @ExtendWith(InstantExecutorExtension::class) @@ -54,7 +54,7 @@ class OverviewViewModelTest : BaseTest() { private lateinit var profilesRepo: DeviceProfilesRepo private lateinit var missingPermissionsFlow: MutableStateFlow> - private lateinit var devicesFlow: MutableStateFlow> + private lateinit var devicesFlow: MutableStateFlow> private lateinit var connectedDevicesFlow: MutableStateFlow> private lateinit var isBluetoothEnabledFlow: MutableStateFlow private lateinit var profilesFlow: MutableStateFlow> @@ -154,7 +154,7 @@ class OverviewViewModelTest : BaseTest() { @Test fun `devices passed through when permissions granted`() = runTest(testDispatcher) { - val device = MonitoredDevice(ble = mockk(relaxed = true), aap = null) + val device = PodDevice(ble = mockk(relaxed = true), aap = null) devicesFlow.value = listOf(device) val vm = createViewModel() @@ -165,17 +165,17 @@ class OverviewViewModelTest : BaseTest() { @Test fun `profiledDevices returns only devices with non-null profile`() { - val withProfile = object : PodDevice.Meta { + val withProfile = object : BlePodSnapshot.Meta { override val profile: DeviceProfile = AppleDeviceProfile(label = "Test") } - val withoutProfile = object : PodDevice.Meta { + val withoutProfile = object : BlePodSnapshot.Meta { override val profile: DeviceProfile? = null } - val profiled = MonitoredDevice( + val profiled = PodDevice( ble = mockk(relaxed = true) { every { meta } returns withProfile }, aap = null, ) - val unmatched = MonitoredDevice( + val unmatched = PodDevice( ble = mockk(relaxed = true) { every { meta } returns withoutProfile }, aap = null, ) @@ -196,17 +196,17 @@ class OverviewViewModelTest : BaseTest() { @Test fun `unmatchedDevices returns only devices with null profile`() { - val withProfile = object : PodDevice.Meta { + val withProfile = object : BlePodSnapshot.Meta { override val profile: DeviceProfile = AppleDeviceProfile(label = "Test") } - val withoutProfile = object : PodDevice.Meta { + val withoutProfile = object : BlePodSnapshot.Meta { override val profile: DeviceProfile? = null } - val profiled = MonitoredDevice( + val profiled = PodDevice( ble = mockk(relaxed = true) { every { meta } returns withProfile }, aap = null, ) - val unmatched = MonitoredDevice( + val unmatched = PodDevice( ble = mockk(relaxed = true) { every { meta } returns withoutProfile }, aap = null, ) diff --git a/app/src/test/java/eu/darken/capod/monitor/core/MonitoredDeviceTest.kt b/app/src/test/java/eu/darken/capod/monitor/core/PodDeviceTest.kt similarity index 85% rename from app/src/test/java/eu/darken/capod/monitor/core/MonitoredDeviceTest.kt rename to app/src/test/java/eu/darken/capod/monitor/core/PodDeviceTest.kt index 741c3706..e92244ad 100644 --- a/app/src/test/java/eu/darken/capod/monitor/core/MonitoredDeviceTest.kt +++ b/app/src/test/java/eu/darken/capod/monitor/core/PodDeviceTest.kt @@ -1,10 +1,10 @@ package eu.darken.capod.monitor.core +import eu.darken.capod.pods.core.BlePodSnapshot import eu.darken.capod.pods.core.HasCase import eu.darken.capod.pods.core.HasChargeDetectionDual import eu.darken.capod.pods.core.HasEarDetection import eu.darken.capod.pods.core.HasEarDetectionDual -import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.PodModel import eu.darken.capod.pods.core.apple.DualApplePods import eu.darken.capod.pods.core.apple.protocol.aap.AapConnectionState @@ -20,15 +20,15 @@ import org.junit.jupiter.api.Test import testhelpers.BaseTest import java.time.Instant -class MonitoredDeviceTest : BaseTest() { +class PodDeviceTest : BaseTest() { private fun mockDualPod( model: PodModel = PodModel.AIRPODS_PRO3, leftBattery: Float? = 0.8f, rightBattery: Float? = 0.9f, caseBattery: Float? = 0.5f, - ): PodDevice { - // DualApplePods implements DualPodDevice + HasCase + other interfaces + ): BlePodSnapshot { + // DualApplePods implements DualBlePodSnapshot + HasCase + other interfaces return mockk(relaxed = true) { every { this@mockk.model } returns model every { batteryLeftPodPercent } returns leftBattery @@ -39,14 +39,14 @@ class MonitoredDeviceTest : BaseTest() { @Test fun `BLE-only device exposes battery from BLE`() { - val device = MonitoredDevice(ble = mockDualPod(leftBattery = 0.8f), aap = null) + val device = PodDevice(ble = mockDualPod(leftBattery = 0.8f), aap = null) device.batteryLeft shouldBe 0.8f device.isAapConnected shouldBe false } @Test fun `capabilities come from model features`() { - val device = MonitoredDevice( + val device = PodDevice( ble = mockDualPod(model = PodModel.AIRPODS_PRO3), aap = null, ) @@ -58,7 +58,7 @@ class MonitoredDeviceTest : BaseTest() { @Test fun `Beats Solo 3 has no dual pods or case`() { - val device = MonitoredDevice( + val device = PodDevice( ble = mockk(relaxed = true) { every { model } returns PodModel.BEATS_SOLO_3 }, aap = null, ) @@ -77,7 +77,7 @@ class MonitoredDeviceTest : BaseTest() { ), ), ) - val device = MonitoredDevice(ble = mockDualPod(), aap = aap) + val device = PodDevice(ble = mockDualPod(), aap = aap) device.isAapConnected shouldBe true device.ancMode.shouldNotBeNull() device.ancMode!!.current shouldBe AncModeValue.TRANSPARENCY @@ -85,21 +85,21 @@ class MonitoredDeviceTest : BaseTest() { @Test fun `ANC mode is null when not AAP connected`() { - val device = MonitoredDevice(ble = mockDualPod(), aap = null) + val device = PodDevice(ble = mockDualPod(), aap = null) device.ancMode.shouldBeNull() } @Test fun `null BLE gives UNKNOWN model`() { - val device = MonitoredDevice(ble = null, aap = null) + val device = PodDevice(ble = null, aap = null) device.model shouldBe PodModel.UNKNOWN } @Test fun `identity properties delegate to BLE`() { - val id = PodDevice.Id() - val meta = mockk(relaxed = true) - val device = MonitoredDevice( + val id = BlePodSnapshot.Id() + val meta = mockk(relaxed = true) + val device = PodDevice( ble = mockk(relaxed = true) { every { identifier } returns id every { this@mockk.meta } returns meta @@ -112,7 +112,7 @@ class MonitoredDeviceTest : BaseTest() { @Test fun `identity properties null when BLE null`() { - val device = MonitoredDevice(ble = null, aap = null) + val device = PodDevice(ble = null, aap = null) device.identifier.shouldBeNull() device.meta.shouldBeNull() } @@ -121,7 +121,7 @@ class MonitoredDeviceTest : BaseTest() { fun `signal timing properties delegate to BLE`() { val now = Instant.now() val earlier = now.minusSeconds(60) - val device = MonitoredDevice( + val device = PodDevice( ble = mockk(relaxed = true) { every { seenLastAt } returns now every { seenFirstAt } returns earlier @@ -138,7 +138,7 @@ class MonitoredDeviceTest : BaseTest() { @Test fun `signal timing defaults when BLE null`() { - val device = MonitoredDevice(ble = null, aap = null) + val device = PodDevice(ble = null, aap = null) device.seenLastAt.shouldBeNull() device.seenFirstAt.shouldBeNull() device.signalQuality shouldBe 0f @@ -153,7 +153,7 @@ class MonitoredDeviceTest : BaseTest() { every { (this@mockk as HasChargeDetectionDual).isRightPodCharging } returns false every { (this@mockk as HasCase).isCaseCharging } returns true } - val device = MonitoredDevice(ble = mock, aap = null) + val device = PodDevice(ble = mock, aap = null) device.isLeftPodCharging shouldBe true device.isRightPodCharging shouldBe false device.isCaseCharging shouldBe true @@ -168,7 +168,7 @@ class MonitoredDeviceTest : BaseTest() { every { (this@mockk as HasEarDetection).isBeingWorn } returns false every { (this@mockk as HasEarDetectionDual).isEitherPodInEar } returns true } - val device = MonitoredDevice(ble = mock, aap = null) + val device = PodDevice(ble = mock, aap = null) device.isLeftInEar shouldBe true device.isRightInEar shouldBe false device.isBeingWorn shouldBe false @@ -177,7 +177,7 @@ class MonitoredDeviceTest : BaseTest() { @Test fun `icon and label properties delegate to BLE`() { - val device = MonitoredDevice( + val device = PodDevice( ble = mockk(relaxed = true) { every { model } returns PodModel.AIRPODS_PRO3 every { iconRes } returns 42 @@ -189,7 +189,7 @@ class MonitoredDeviceTest : BaseTest() { @Test fun `rawDataHex empty when BLE null`() { - val device = MonitoredDevice(ble = null, aap = null) + val device = PodDevice(ble = null, aap = null) device.rawDataHex shouldBe emptyList() } } diff --git a/app/src/test/java/eu/darken/capod/pods/core/apple/AirPodsFactoryTest.kt b/app/src/test/java/eu/darken/capod/pods/core/apple/AirPodsFactoryTest.kt index 6b615422..929817f7 100644 --- a/app/src/test/java/eu/darken/capod/pods/core/apple/AirPodsFactoryTest.kt +++ b/app/src/test/java/eu/darken/capod/pods/core/apple/AirPodsFactoryTest.kt @@ -1,9 +1,9 @@ package eu.darken.capod.pods.core.apple -import eu.darken.capod.pods.core.PodDevice +import eu.darken.capod.pods.core.BlePodSnapshot import eu.darken.capod.pods.core.apple.airpods.AirPodsGen1 import eu.darken.capod.pods.core.apple.airpods.AirPodsPro -import eu.darken.capod.pods.core.apple.misc.UnknownAppleDevice +import eu.darken.capod.pods.core.apple.misc.UnknownAppleSnapshotBle import io.kotest.matchers.shouldBe import io.kotest.matchers.types.instanceOf import kotlinx.coroutines.test.runTest @@ -28,13 +28,13 @@ class AirPodsFactoryTest : BaseAirPodsTest() { @Test fun `unknown AppleDevice`() = runTest { create("07 19 01 FF FF 2B 99 8F 01 00 >09< 10 30 EE F3 41 B5 D8 9F A3 B0 B4 17 9F 85 97 5F") { - this shouldBe instanceOf() + this shouldBe instanceOf() } } @Test fun `invalid data`() = runTest { - create("abcd") { + create("abcd") { this shouldBe null } } diff --git a/app/src/test/java/eu/darken/capod/pods/core/apple/BaseAirPodsTest.kt b/app/src/test/java/eu/darken/capod/pods/core/apple/BaseAirPodsTest.kt index 0f67ea61..4eac66c7 100644 --- a/app/src/test/java/eu/darken/capod/pods/core/apple/BaseAirPodsTest.kt +++ b/app/src/test/java/eu/darken/capod/pods/core/apple/BaseAirPodsTest.kt @@ -6,7 +6,7 @@ import eu.darken.capod.common.SystemClockWrap import eu.darken.capod.common.bluetooth.BleScanResult import eu.darken.capod.common.fromHex import eu.darken.capod.common.serialization.SerializationModule -import eu.darken.capod.pods.core.PodDevice +import eu.darken.capod.pods.core.BlePodSnapshot import eu.darken.capod.pods.core.apple.protocol.ContinuityProtocol import eu.darken.capod.profiles.core.DeviceProfile import eu.darken.capod.profiles.core.DeviceProfilesRepo @@ -60,7 +60,7 @@ abstract class BaseAirPodsTest : BaseTest() { every { SystemClockWrap.elapsedRealtimeNanos } returns 1000L } - internal suspend inline fun create( + internal suspend inline fun create( hex: String, address: String = "77:49:4C:D8:25:0C", block: T.() -> Unit