Refactor time access behind TimeSource

This commit is contained in:
darken
2026-04-14 11:28:41 +02:00
committed by Matthias Urhahn
parent c1601c105c
commit 1cd8ae5ace
71 changed files with 362 additions and 212 deletions
@@ -1,7 +1,6 @@
package eu.darken.capod.common
import android.media.AudioManager
import android.os.SystemClock
import android.view.KeyEvent
import eu.darken.capod.common.debug.logging.Logging.Priority.INFO
import eu.darken.capod.common.debug.logging.log
@@ -13,6 +12,7 @@ import javax.inject.Singleton
@Singleton
class MediaControl @Inject constructor(
private val audioManager: AudioManager,
private val timeSource: TimeSource,
) {
private var capPauseExpiryElapsedRealtime: Long = 0L
@@ -20,7 +20,7 @@ class MediaControl @Inject constructor(
get() = audioManager.isMusicActive
val wasRecentlyPausedByCap: Boolean
get() = capPauseExpiryElapsedRealtime > SystemClock.elapsedRealtime()
get() = capPauseExpiryElapsedRealtime > timeSource.elapsedRealtime()
suspend fun sendPlay() {
log(TAG, INFO) { "sendPlay()" }
@@ -57,7 +57,7 @@ class MediaControl @Inject constructor(
internal suspend fun sendKey(keyCode: Int) {
log(TAG) { "Sending up+down KeyEvent: $keyCode" }
val eventTime = SystemClock.uptimeMillis()
val eventTime = timeSource.uptimeMillis()
audioManager.dispatchMediaKeyEvent(KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, keyCode, 0))
delay(100)
audioManager.dispatchMediaKeyEvent(KeyEvent(eventTime + 200, eventTime + 200, KeyEvent.ACTION_UP, keyCode, 0))
@@ -82,7 +82,7 @@ class MediaControl @Inject constructor(
}
private fun markRecentCapPause() {
capPauseExpiryElapsedRealtime = SystemClock.elapsedRealtime() + RECENT_CAP_PAUSE_WINDOW_MS
capPauseExpiryElapsedRealtime = timeSource.elapsedRealtime() + RECENT_CAP_PAUSE_WINDOW_MS
}
private fun clearRecentCapPause() {
@@ -1,9 +0,0 @@
package eu.darken.capod.common
import android.os.SystemClock
object SystemClockWrap {
val elapsedRealtimeNanos: Long
get() = SystemClock.elapsedRealtimeNanos()
}
@@ -0,0 +1,14 @@
package eu.darken.capod.common
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
@InstallIn(SingletonComponent::class)
@Module
abstract class TimeModule {
@Binds
abstract fun timeSource(defaultTimeSource: DefaultTimeSource): TimeSource
}
@@ -0,0 +1,29 @@
package eu.darken.capod.common
import android.os.SystemClock
import java.time.Instant
import javax.inject.Inject
import javax.inject.Singleton
interface TimeSource {
fun now(): Instant
fun currentTimeMillis(): Long
fun elapsedRealtime(): Long
fun elapsedRealtimeNanos(): Long
fun uptimeMillis(): Long
}
object SystemTimeSource : TimeSource {
override fun now(): Instant = Instant.now()
override fun currentTimeMillis(): Long = System.currentTimeMillis()
override fun elapsedRealtime(): Long = SystemClock.elapsedRealtime()
override fun elapsedRealtimeNanos(): Long = SystemClock.elapsedRealtimeNanos()
override fun uptimeMillis(): Long = SystemClock.uptimeMillis()
}
@Singleton
class DefaultTimeSource @Inject constructor() : TimeSource by SystemTimeSource
@@ -3,6 +3,8 @@ package eu.darken.capod.common.bluetooth
import android.bluetooth.le.ScanResult
import android.os.Parcelable
import androidx.core.util.forEach
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.TimeSource
import eu.darken.capod.common.serialization.InstantEpochMillisSerializer
import eu.darken.capod.common.serialization.MapIntByteArrayBase64Serializer
import kotlinx.parcelize.Parcelize
@@ -31,8 +33,11 @@ data class BleScanResult(
}
companion object {
fun fromScanResult(scanResult: ScanResult) = BleScanResult(
receivedAt = Instant.now(),
fun fromScanResult(
scanResult: ScanResult,
timeSource: TimeSource = SystemTimeSource,
) = BleScanResult(
receivedAt = timeSource.now(),
address = scanResult.device.address,
rssi = scanResult.rssi,
generatedAtNanos = scanResult.timestampNanos,
@@ -43,4 +48,4 @@ data class BleScanResult(
}
)
}
}
}
@@ -9,6 +9,7 @@ import android.bluetooth.le.ScanSettings
import android.content.Context
import android.content.Intent
import dagger.hilt.android.qualifiers.ApplicationContext
import eu.darken.capod.common.TimeSource
import eu.darken.capod.common.debug.logging.Logging.Priority.DEBUG
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
@@ -33,6 +34,7 @@ class BleScanner @Inject constructor(
private val bluetoothManager: BluetoothManager2,
private val fakeBleData: FakeBleData,
private val scanResultForwarder: BleScanResultForwarder,
private val timeSource: TimeSource,
) {
@SuppressLint("MissingPermission") fun scan(
@@ -71,15 +73,15 @@ class BleScanner @Inject constructor(
if (!passed) log(TAG, VERBOSE) { "Manually filtered $result" }
passed
}
.map { BleScanResult.fromScanResult(it) }
.map { BleScanResult.fromScanResult(it, timeSource) }
}
val callback = object : ScanCallback() {
var lastScanAt = System.currentTimeMillis()
var lastScanAt = timeSource.currentTimeMillis()
override fun onScanResult(callbackType: Int, result: ScanResult) {
log(TAG, VERBOSE) {
val delay = System.currentTimeMillis() - lastScanAt
lastScanAt = System.currentTimeMillis()
val delay = timeSource.currentTimeMillis() - lastScanAt
lastScanAt = timeSource.currentTimeMillis()
"onScanResult(delay=${delay}ms, callbackType=$callbackType, result=$result)"
}
@@ -88,8 +90,8 @@ class BleScanner @Inject constructor(
override fun onBatchScanResults(results: MutableList<ScanResult>) {
log(TAG, VERBOSE) {
val delay = System.currentTimeMillis() - lastScanAt
lastScanAt = System.currentTimeMillis()
val delay = timeSource.currentTimeMillis() - lastScanAt
lastScanAt = timeSource.currentTimeMillis()
"onBatchScanResults(delay=${delay}ms, results=$results)"
}
@@ -210,4 +212,4 @@ class BleScanner @Inject constructor(
private const val CALLBACK_INTENT_REQUESTCODE = 270
private val TAG = logTag("Bluetooth", "BleScanner")
}
}
}
@@ -13,6 +13,7 @@ import android.content.IntentFilter
import android.os.Handler
import android.os.HandlerThread
import dagger.hilt.android.qualifiers.ApplicationContext
import eu.darken.capod.common.TimeSource
import eu.darken.capod.common.coroutine.AppScope
import eu.darken.capod.common.coroutine.DispatcherProvider
import eu.darken.capod.common.debug.Bugs
@@ -53,6 +54,7 @@ class BluetoothManager2 @Inject constructor(
private val dispatcherProvider: DispatcherProvider,
@ApplicationContext private val context: Context,
private val manager: BluetoothManager,
private val timeSource: TimeSource,
) {
val adapter: BluetoothAdapter?
@@ -255,8 +257,10 @@ class BluetoothManager2 @Inject constructor(
BluetoothDevice2(
internal = device,
seenFirstAt = seenDevicesLock.withLock {
seenDevicesCache[device.address] ?: Instant.now().also {
seenDevicesCache[device.address] = it
seenDevicesCache[device.address] ?: run {
val now = timeSource.now()
seenDevicesCache[device.address] = now
now
}
}
)
@@ -294,8 +298,10 @@ class BluetoothManager2 @Inject constructor(
BluetoothDevice2(
internal = device,
seenFirstAt = seenDevicesLock.withLock {
seenDevicesCache[device.address] ?: Instant.now().also {
seenDevicesCache[device.address] = it
seenDevicesCache[device.address] ?: run {
val now = timeSource.now()
seenDevicesCache[device.address] = now
now
}
}
)
@@ -364,4 +370,4 @@ class BluetoothManager2 @Inject constructor(
companion object {
private val TAG = logTag("Bluetooth", "Manager2")
}
}
}
@@ -1,10 +1,9 @@
package eu.darken.capod.common.bluetooth
import dagger.Reusable
import eu.darken.capod.common.SystemClockWrap
import eu.darken.capod.common.TimeSource
import eu.darken.capod.common.debug.DebugSettings
import eu.darken.capod.common.fromHex
import java.time.Instant
import javax.inject.Inject
import kotlin.random.Random
import eu.darken.capod.common.datastore.valueBlocking
@@ -12,6 +11,7 @@ import eu.darken.capod.common.datastore.valueBlocking
@Reusable
class FakeBleData @Inject constructor(
private val debugSettings: DebugSettings,
private val timeSource: TimeSource,
) {
fun maybeAddfakeData(originals: Collection<BleScanResult>): Collection<BleScanResult> {
@@ -21,12 +21,14 @@ class FakeBleData @Inject constructor(
fun getFakeData(): Collection<BleScanResult> {
val fakeDevices = mutableListOf<BleScanResult>()
val now = timeSource.now()
val generatedAt = timeSource.elapsedRealtimeNanos()
// AirPods Gen1
BleScanResult(
receivedAt = Instant.now(),
receivedAt = now,
address = "78:73:AF:B4:85:22",
rssi = Random.nextInt(100) * -1,
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 100,
generatedAtNanos = generatedAt + 100,
manufacturerSpecificData = mapOf(76 to "07 19 01 02 20 75 AA B6 31 00 05 9C 5A A4 5D C0 2C A0 B4 6F B9 ED 8E CE 03 97 CA".fromHex())
).run {
fakeDevices.add(this)
@@ -34,10 +36,10 @@ class FakeBleData @Inject constructor(
// AirPods Gen2
BleScanResult(
receivedAt = Instant.now(),
receivedAt = now,
address = "78:73:FF:B4:85:5E",
rssi = Random.nextInt(100) * -1,
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 100,
generatedAtNanos = generatedAt + 100,
manufacturerSpecificData = mapOf(76 to "07 19 01 0F 20 75 AA B6 31 00 05 9C 5A A4 5D C0 2C A0 B4 6F B9 ED 8E CE 03 97 CA".fromHex())
).run {
fakeDevices.add(this)
@@ -45,30 +47,30 @@ class FakeBleData @Inject constructor(
// AirPods Gen3
BleScanResult(
receivedAt = Instant.now(),
receivedAt = now,
address = "4E:9E:D1:49:D2:6D",
rssi = Random.nextInt(15, 75) * -1,
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 200,
generatedAtNanos = generatedAt + 200,
manufacturerSpecificData = mapOf(76 to "07 19 01 13 20 55 AF 56 31 00 06 6F E4 DF 10 AF 10 60 81 03 3B 76 D9 C7 11 22 88".fromHex())
).run {
fakeDevices.add(this)
}
// AirPods Max
BleScanResult(
receivedAt = Instant.now(),
receivedAt = now,
address = "7E:E5:C7:65:D2:B5",
rssi = Random.nextInt(15, 75) * -1,
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 300,
generatedAtNanos = generatedAt + 300,
manufacturerSpecificData = mapOf(76 to "07 19 01 0A 20 02 05 80 04 0F 44 A7 60 9B F8 3C FD B1 D8 1C 61 EA 82 60 A3 2C 4E".fromHex())
).run {
fakeDevices.add(this)
}
// BeatsFlex
BleScanResult(
receivedAt = Instant.now(),
receivedAt = now,
address = "5E:9E:D1:49:D2:6D",
rssi = Random.nextInt(15, 75) * -1,
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 400,
generatedAtNanos = generatedAt + 400,
manufacturerSpecificData = mapOf(76 to "07 19 01 10 20 0A F4 8F 00 01 00 C4 71 9F 9C EF A2 E3 BA 66 FE 1D 45 9F C9 2F A0".fromHex())
).run {
fakeDevices.add(this)
@@ -76,10 +78,10 @@ class FakeBleData @Inject constructor(
// Tws i99999
BleScanResult(
receivedAt = Instant.now(),
receivedAt = now,
address = "5E:9E:D1:29:D2:6D",
rssi = Random.nextInt(15, 75) * -1,
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 400,
generatedAtNanos = generatedAt + 400,
manufacturerSpecificData = mapOf(76 to "07 13 01 02 20 71 AA 37 32 00 10 00 64 64 FF 00 00 00 00 00 00".fromHex())
).run {
fakeDevices.add(this)
@@ -87,10 +89,10 @@ class FakeBleData @Inject constructor(
// Unknown Device
BleScanResult(
receivedAt = Instant.now(),
receivedAt = now,
address = "6E:9E:D1:49:D2:6D",
rssi = Random.nextInt(15, 75) * -1,
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 500,
generatedAtNanos = generatedAt + 500,
manufacturerSpecificData = mapOf(76 to "07 19 01 FF 20 0A F4 8F 00 01 00 C4 71 9F 9C EF A2 E3 BA 66 FE 1D 45 9F C9 2F A0".fromHex())
).run {
fakeDevices.add(this)
@@ -98,4 +100,4 @@ class FakeBleData @Inject constructor(
return fakeDevices
}
}
}
@@ -2,6 +2,7 @@ package eu.darken.capod.common.debug.logging
import android.annotation.SuppressLint
import android.util.Log
import eu.darken.capod.common.TimeSource
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
@@ -10,7 +11,10 @@ import java.time.Instant
@SuppressLint("LogNotTimber")
class FileLogger(private val logFile: File) : Logging.Logger {
class FileLogger(
private val logFile: File,
private val timeSource: TimeSource,
) : Logging.Logger {
private var logWriter: OutputStreamWriter? = null
@SuppressLint("SetWorldReadable")
@@ -57,7 +61,7 @@ class FileLogger(private val logFile: File) : Logging.Logger {
override fun log(priority: Logging.Priority, tag: String, message: String, metaData: Map<String, Any>?) {
logWriter?.let {
try {
it.write("${Instant.ofEpochMilli(System.currentTimeMillis())} ${priority.shortLabel}/$tag: $message\n")
it.write("${timeSource.now()} ${priority.shortLabel}/$tag: $message\n")
it.flush()
} catch (e: IOException) {
Log.e(TAG, "Failed to write log line.", e)
@@ -76,4 +80,3 @@ class FileLogger(private val logFile: File) : Logging.Logger {
private val TAG = logTag("Debug", "FileLogger")
}
}
@@ -1,5 +1,6 @@
package eu.darken.capod.common.debug.recording.core
import eu.darken.capod.common.TimeSource
import eu.darken.capod.common.debug.logging.FileLogger
import eu.darken.capod.common.debug.logging.Logging
import eu.darken.capod.common.debug.logging.Logging.Priority.INFO
@@ -10,7 +11,9 @@ import kotlinx.coroutines.sync.withLock
import java.io.File
import javax.inject.Inject
class Recorder @Inject constructor() {
class Recorder @Inject constructor(
private val timeSource: TimeSource,
) {
private val mutex = Mutex()
private var fileLogger: FileLogger? = null
@@ -23,7 +26,7 @@ class Recorder @Inject constructor() {
suspend fun start(path: File) = mutex.withLock {
if (fileLogger != null) return@withLock
this.path = path
fileLogger = FileLogger(path)
fileLogger = FileLogger(path, timeSource)
fileLogger?.let {
it.start()
Logging.install(it)
@@ -45,4 +48,4 @@ class Recorder @Inject constructor() {
internal val TAG = logTag("Debug", "Log", "Recorder")
}
}
}
@@ -7,6 +7,8 @@ import androidx.annotation.VisibleForTesting
import dagger.hilt.android.qualifiers.ApplicationContext
import eu.darken.capod.common.BuildConfigWrap
import eu.darken.capod.common.InstallId
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.TimeSource
import eu.darken.capod.common.coroutine.AppScope
import eu.darken.capod.common.coroutine.DispatcherProvider
import eu.darken.capod.common.debug.logging.Logging.Priority.ERROR
@@ -32,6 +34,7 @@ class RecorderModule @Inject constructor(
@AppScope private val appScope: CoroutineScope,
private val dispatcherProvider: DispatcherProvider,
private val installId: InstallId,
private val timeSource: TimeSource,
) {
@Volatile
@@ -73,11 +76,11 @@ class RecorderModule @Inject constructor(
createSessionDir()
}
val logFile = File(sessionDir, "core.log")
val newRecorder = Recorder()
val newRecorder = Recorder(timeSource)
newRecorder.start(logFile)
if (!isResume) {
val startTime = System.currentTimeMillis()
val startTime = timeSource.currentTimeMillis()
writeTriggerFile(sessionDir, startTime)
log(TAG, INFO) { "Build.Fingerprint: ${Build.FINGERPRINT}" }
log(TAG, INFO) { "BuildConfig.Versions: ${BuildConfigWrap.VERSION_DESCRIPTION}" }
@@ -99,7 +102,7 @@ class RecorderModule @Inject constructor(
copy(
recorder = newRecorder,
currentLogDir = sessionDir,
recordingStartedAt = if (recordingStartedAt > 0L) recordingStartedAt else System.currentTimeMillis(),
recordingStartedAt = if (recordingStartedAt > 0L) recordingStartedAt else timeSource.currentTimeMillis(),
persistedLogDir = null,
)
}
@@ -126,7 +129,7 @@ class RecorderModule @Inject constructor(
}
private fun createSessionDir(): File {
val timestamp = java.time.ZonedDateTime.now(java.time.ZoneOffset.UTC)
val timestamp = timeSource.now().atZone(java.time.ZoneOffset.UTC)
.format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'"))
val installIdPrefix = installId.id.take(8)
val dirName = "capod_${BuildConfigWrap.VERSION_NAME}_${timestamp}_$installIdPrefix"
@@ -179,7 +182,7 @@ class RecorderModule @Inject constructor(
if (!currentState.isRecording) return StopResult.NotRecording
val logDir = currentState.currentLogDir ?: return StopResult.NotRecording
val elapsed = System.currentTimeMillis() - currentState.recordingStartedAt
val elapsed = timeSource.currentTimeMillis() - currentState.recordingStartedAt
if (elapsed < MIN_RECORDING_MS) return StopResult.TooShort
stopRecorder()
@@ -235,7 +238,7 @@ class RecorderModule @Inject constructor(
@VisibleForTesting
internal fun parseTriggerContent(
content: String,
now: Long = System.currentTimeMillis(),
now: Long = SystemTimeSource.currentTimeMillis(),
): Pair<File, Long>? {
val trimmed = content.trim()
if (trimmed.isEmpty()) return null
@@ -8,6 +8,8 @@ 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.SingleEventFlow
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.TimeSource
import eu.darken.capod.common.uix.ViewModel4
import eu.darken.capod.main.core.GeneralSettings
import eu.darken.capod.main.core.MonitorMode
@@ -46,6 +48,7 @@ class DeviceSettingsViewModel @Inject constructor(
private val bluetoothManager: BluetoothManager2,
private val profilesRepo: DeviceProfilesRepo,
private val generalSettings: GeneralSettings,
private val timeSource: TimeSource,
) : ViewModel4(dispatcherProvider) {
private val targetProfileId = MutableStateFlow<ProfileId?>(null)
@@ -88,7 +91,7 @@ class DeviceSettingsViewModel @Inject constructor(
val connectedAddresses = connectedDevices.map { it.address }.toSet()
State(
device = device,
now = Instant.now(),
now = timeSource.now(),
isPro = upgrade.isPro,
isNudgeAvailable = bluetoothManager.isNudgeAvailable,
isForceConnecting = forcing,
@@ -110,7 +113,7 @@ class DeviceSettingsViewModel @Inject constructor(
data class State(
val device: PodDevice?,
val now: Instant = Instant.now(),
val now: Instant = SystemTimeSource.now(),
val isPro: Boolean = false,
val isNudgeAvailable: Boolean = true,
val isForceConnecting: Boolean = false,
@@ -38,6 +38,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import eu.darken.capod.R
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.compose.Preview2
import eu.darken.capod.common.compose.PreviewWrapper
import eu.darken.capod.common.compose.preview.MockPodDataProvider
@@ -327,7 +328,7 @@ private fun PodDeviceCard(
private fun OverviewScreenWithDevicesPreview() = PreviewWrapper {
OverviewScreen(
state = OverviewViewModel.State(
now = Instant.now(),
now = SystemTimeSource.now(),
permissions = emptySet(),
devices = listOf(
MockPodDataProvider.dualPodMonitoredMixed(),
@@ -356,7 +357,7 @@ private fun OverviewScreenWithDevicesPreview() = PreviewWrapper {
private fun OverviewScreenEmptyPreview() = PreviewWrapper {
OverviewScreen(
state = OverviewViewModel.State(
now = Instant.now(),
now = SystemTimeSource.now(),
permissions = emptySet(),
devices = emptyList(),
isDebugMode = false,
@@ -378,7 +379,7 @@ private fun OverviewScreenEmptyPreview() = PreviewWrapper {
private fun OverviewScreenNoProfilesPreview() = PreviewWrapper {
OverviewScreen(
state = OverviewViewModel.State(
now = Instant.now(),
now = SystemTimeSource.now(),
permissions = emptySet(),
devices = emptyList(),
isDebugMode = false,
@@ -400,7 +401,7 @@ private fun OverviewScreenNoProfilesPreview() = PreviewWrapper {
private fun OverviewScreenBluetoothOffPreview() = PreviewWrapper {
OverviewScreen(
state = OverviewViewModel.State(
now = Instant.now(),
now = SystemTimeSource.now(),
permissions = emptySet(),
devices = emptyList(),
isDebugMode = false,
@@ -12,6 +12,7 @@ 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
import eu.darken.capod.common.TimeSource
import eu.darken.capod.common.uix.ViewModel4
import eu.darken.capod.common.upgrade.UpgradeRepo
import eu.darken.capod.main.core.GeneralSettings
@@ -50,6 +51,7 @@ class OverviewViewModel @Inject constructor(
private val bluetoothManager: BluetoothManager2,
private val profilesRepo: DeviceProfilesRepo,
private val aapManager: AapConnectionManager,
private val timeSource: TimeSource,
) : ViewModel4(dispatcherProvider) {
val requestPermissionEvent = SingleEventFlow<Permission>()
@@ -112,7 +114,7 @@ class OverviewViewModel @Inject constructor(
showUnmatchedDevices,
) { _, permissions, devices, isDebugMode, isBluetoothEnabled, profiles, upgradeInfo, showUnmatched ->
State(
now = Instant.now(),
now = timeSource.now(),
permissions = permissions,
devices = devices,
isDebugMode = isDebugMode,
@@ -43,6 +43,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import eu.darken.capod.R
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.compose.Preview2
import eu.darken.capod.common.compose.PreviewWrapper
import eu.darken.capod.common.compose.preview.MockPodDataProvider
@@ -415,7 +416,7 @@ private fun DualPodsCardFullPreview() = PreviewWrapper {
DualPodsCard(
device = MockPodDataProvider.dualPodMonitoredWithAap(),
showDebug = false,
now = Instant.now(),
now = SystemTimeSource.now(),
isPro = false,
onDeviceSettings = {},
)
@@ -427,7 +428,7 @@ private fun DualPodsCardMinimalPreview() = PreviewWrapper {
DualPodsCard(
device = MockPodDataProvider.dualPodMonitored(),
showDebug = false,
now = Instant.now(),
now = SystemTimeSource.now(),
)
}
@@ -437,7 +438,7 @@ private fun DualPodsCardCachedPreview() = PreviewWrapper {
DualPodsCard(
device = MockPodDataProvider.dualPodCachedOnly(),
showDebug = false,
now = Instant.now(),
now = SystemTimeSource.now(),
)
}
@@ -447,7 +448,7 @@ private fun DualPodsCardMissingAddressPreview() = PreviewWrapper {
DualPodsCard(
device = MockPodDataProvider.dualPodMonitoredMixed(),
showDebug = false,
now = Instant.now(),
now = SystemTimeSource.now(),
onEditProfile = {},
)
}
@@ -43,6 +43,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import eu.darken.capod.R
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.compose.Preview2
import eu.darken.capod.common.compose.PreviewWrapper
import eu.darken.capod.common.compose.preview.MockPodDataProvider
@@ -286,7 +287,7 @@ private fun SinglePodsCardFullPreview() = PreviewWrapper {
SinglePodsCard(
device = MockPodDataProvider.singlePodMonitoredWithAap(),
showDebug = false,
now = Instant.now(),
now = SystemTimeSource.now(),
onDeviceSettings = {},
)
}
@@ -297,7 +298,7 @@ private fun SinglePodsCardMinimalPreview() = PreviewWrapper {
SinglePodsCard(
device = MockPodDataProvider.singlePodMonitored(),
showDebug = false,
now = Instant.now(),
now = SystemTimeSource.now(),
)
}
@@ -307,7 +308,7 @@ private fun SinglePodsCardCachedPreview() = PreviewWrapper {
SinglePodsCard(
device = MockPodDataProvider.singlePodCachedOnly(),
showDebug = false,
now = Instant.now(),
now = SystemTimeSource.now(),
)
}
@@ -317,7 +318,7 @@ private fun SinglePodsCardMissingAddressPreview() = PreviewWrapper {
SinglePodsCard(
device = MockPodDataProvider.singlePodMonitored(),
showDebug = false,
now = Instant.now(),
now = SystemTimeSource.now(),
onEditProfile = {},
)
}
@@ -53,6 +53,7 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import eu.darken.capod.R
import eu.darken.capod.common.PrivacyPolicy
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.WebpageTool
import eu.darken.capod.common.compose.ConfirmationDialog
import eu.darken.capod.common.compose.Preview2
@@ -452,7 +453,7 @@ private fun SessionRow(
)
val agoText = DateUtils.getRelativeTimeSpanString(
session.createdAt.toEpochMilli(),
System.currentTimeMillis(),
SystemTimeSource.currentTimeMillis(),
DateUtils.SECOND_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE,
)
@@ -58,6 +58,7 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.LifecycleResumeEffect
import eu.darken.capod.R
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.PrivacyPolicy
import eu.darken.capod.common.WebpageTool
import eu.darken.capod.common.compose.ConfirmationDialog
@@ -338,7 +339,7 @@ fun ContactFormScreen(
val sizeText = Formatter.formatShortFileSize(context, session.diskSize)
val agoText = DateUtils.getRelativeTimeSpanString(
session.createdAt.toEpochMilli(),
System.currentTimeMillis(),
SystemTimeSource.currentTimeMillis(),
DateUtils.SECOND_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE,
)
@@ -1,6 +1,7 @@
package eu.darken.capod.monitor.core
import eu.darken.capod.common.bluetooth.BluetoothAddress
import eu.darken.capod.common.TimeSource
import eu.darken.capod.common.coroutine.AppScope
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
import eu.darken.capod.common.debug.logging.log
@@ -42,6 +43,7 @@ class DeviceMonitor @Inject constructor(
private val deviceStateCache: DeviceStateCache,
private val profilesRepo: DeviceProfilesRepo,
private val aapLifecycleManager: AapLifecycleManager,
private val timeSource: TimeSource,
) {
init {
aapLifecycleManager.start()
@@ -155,7 +157,7 @@ class DeviceMonitor @Inject constructor(
for (device in devices) {
val profileId = device.profileId ?: continue
val existing = deviceStateCache.cachedStates.value[profileId]
val newState = device.toCachedState(existing) ?: continue
val newState = device.toCachedState(existing, timeSource.now()) ?: continue
log(TAG, VERBOSE) { "Persisting state for $profileId" }
deviceStateCache.save(profileId, newState)
@@ -3,6 +3,7 @@ package eu.darken.capod.monitor.core
import android.content.Context
import androidx.compose.runtime.Stable
import eu.darken.capod.R
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BluetoothAddress
import eu.darken.capod.monitor.core.cache.CachedDeviceState
import eu.darken.capod.pods.core.apple.PodModel
@@ -73,7 +74,7 @@ data class PodDevice(
val signalQuality: Float
get() {
val bleQuality = ble?.signalQuality ?: 0f
return (bleQuality + computeAapBoost(Instant.now())).coerceAtMost(1f)
return (bleQuality + computeAapBoost(SystemTimeSource.now())).coerceAtMost(1f)
}
/**
@@ -1,6 +1,7 @@
package eu.darken.capod.monitor.core.ble
import android.bluetooth.le.ScanFilter
import eu.darken.capod.common.TimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.bluetooth.BleScanner
import eu.darken.capod.common.bluetooth.BluetoothManager2
@@ -44,6 +45,7 @@ class BlePodMonitor @Inject constructor(
@AppScope private val appScope: CoroutineScope,
private val bleScanner: BleScanner,
private val podFactory: PodFactory,
private val timeSource: TimeSource,
private val generalSettings: GeneralSettings,
bluetoothManager: BluetoothManager2,
private val debugSettings: DebugSettings,
@@ -97,7 +99,7 @@ class BlePodMonitor @Inject constructor(
.replayingShare(appScope)
private suspend fun sortPodsToInterest(devices: Collection<BlePodSnapshot>): List<BlePodSnapshot> {
val now = Instant.now()
val now = timeSource.now()
val profiles = profilesRepo.currentProfiles() ?: emptyList()
return devices.sortedWith(
@@ -175,7 +177,7 @@ class BlePodMonitor @Inject constructor(
return emptyMap()
}
val now = Instant.now()
val now = timeSource.now()
deviceCache.toList().forEach { (key, value) ->
if (Duration.between(value.seenLastAt, now) > STALE_DEVICE_TIMEOUT) {
log(TAG, Logging.Priority.VERBOSE) { "Removing stale device from cache: $value" }
@@ -200,4 +202,4 @@ class BlePodMonitor @Inject constructor(
private val STALE_DEVICE_TIMEOUT = Duration.ofSeconds(20)
private val STALE_EVICTION_INTERVAL = Duration.ofSeconds(10)
}
}
}
@@ -1,5 +1,6 @@
package eu.darken.capod.monitor.core.cache
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.monitor.core.PodDevice
import eu.darken.capod.monitor.core.cache.CachedDeviceState.CachedBatterySlot
import eu.darken.capod.pods.core.apple.ble.DualBlePodSnapshot
@@ -18,7 +19,7 @@ import java.time.Instant
*/
fun PodDevice.toCachedState(
existing: CachedDeviceState?,
now: Instant = Instant.now(),
now: Instant = SystemTimeSource.now(),
): CachedDeviceState? {
if (!isLive) return null
val pid = profileId ?: return null
@@ -3,6 +3,7 @@ package eu.darken.capod.pods.core.apple.aap
import android.annotation.SuppressLint
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothSocket
import eu.darken.capod.common.TimeSource
import eu.darken.capod.common.bluetooth.l2cap.L2capSocketFactory
import eu.darken.capod.common.debug.logging.Logging.Priority.ERROR
import eu.darken.capod.common.debug.logging.Logging.Priority.INFO
@@ -48,6 +49,7 @@ internal class AapConnection(
private val device: BluetoothDevice,
private val profile: AapDeviceProfile,
private val socketFactory: L2capSocketFactory,
private val timeSource: TimeSource,
private val psm: Int = 0x1001,
) {
@@ -168,7 +170,7 @@ internal class AapConnection(
if (currentAnc != null) {
_state.value = currentState
.withSetting(AapSetting.AncMode::class, currentAnc.copy(current = command.mode))
.copy(pendingAncMode = null, lastMessageAt = Instant.now())
.copy(pendingAncMode = null, lastMessageAt = timeSource.now())
} else {
_state.value = currentState.copy(pendingAncMode = null)
}
@@ -258,11 +260,11 @@ internal class AapConnection(
is AapCommand.SetDeviceName -> {
val currentInfo = baseState.deviceInfo ?: return
_state.value = baseState
.copy(deviceInfo = currentInfo.copy(name = command.name), lastMessageAt = Instant.now())
.copy(deviceInfo = currentInfo.copy(name = command.name), lastMessageAt = timeSource.now())
return
}
}
_state.value = baseState.withSetting(updated.first, updated.second).copy(lastMessageAt = Instant.now())
_state.value = baseState.withSetting(updated.first, updated.second).copy(lastMessageAt = timeSource.now())
}
private suspend fun sendRaw(command: AapCommand) {
@@ -272,7 +274,7 @@ internal class AapConnection(
val sock = socket ?: throw IOException("Socket is null")
sock.outputStream.write(bytes)
sock.outputStream.flush()
if (command is AapCommand.SetAncMode) lastAncCommandSentAt = System.currentTimeMillis()
if (command is AapCommand.SetAncMode) lastAncCommandSentAt = timeSource.currentTimeMillis()
log(TAG) { "Sent command: $command (${bytes.size} bytes)" }
}
}
@@ -360,7 +362,7 @@ internal class AapConnection(
val valid = batteries.filterValues { it.charging != AapPodState.ChargingState.DISCONNECTED }
_state.value = _state.value.copy(
batteries = _state.value.batteries + valid,
lastMessageAt = Instant.now(),
lastMessageAt = timeSource.now(),
)
log(TAG) { "Battery update: ${batteries.entries.map { "${it.key}=${(it.value.percent * 100).toInt()}% ${it.value.charging}" }}" }
return
@@ -369,14 +371,14 @@ internal class AapConnection(
// Try private key response
profile.decodePrivateKeyResponse(message)?.let { keys ->
log(TAG) { "Private keys received: IRK=${keys.irk != null}, ENC=${keys.encKey != null}" }
_state.value = _state.value.copy(lastMessageAt = Instant.now())
_state.value = _state.value.copy(lastMessageAt = timeSource.now())
_keysReceived.tryEmit(keys)
return
}
// Try device info
profile.decodeDeviceInfo(message)?.let { info ->
_state.value = _state.value.copy(deviceInfo = info, lastMessageAt = Instant.now())
_state.value = _state.value.copy(deviceInfo = info, lastMessageAt = timeSource.now())
log(TAG) { "Device info: ${info.name} (${info.modelNumber})" }
return
}
@@ -387,10 +389,10 @@ internal class AapConnection(
// Skip debounce for: first ANC mode (initial setup), echoes after our own command.
if (value is AapSetting.AncMode) {
val isFirstAncMode = _state.value.setting<AapSetting.AncMode>() == null
val sinceLastCommand = System.currentTimeMillis() - lastAncCommandSentAt
val sinceLastCommand = timeSource.currentTimeMillis() - lastAncCommandSentAt
if (isFirstAncMode || sinceLastCommand <= 3000L) {
ancDebounceJob?.cancel()
_state.value = _state.value.withSetting(key, value).copy(lastMessageAt = Instant.now())
_state.value = _state.value.withSetting(key, value).copy(lastMessageAt = timeSource.now())
log(TAG) { "Setting: ${key.simpleName} = $value" }
// After our command, firmware may cycle through modes before settling.
@@ -413,7 +415,7 @@ internal class AapConnection(
ancDebounceJob?.cancel()
ancDebounceJob = connectionScope?.launch {
delay(1500L)
_state.value = _state.value.withSetting(key, value).copy(lastMessageAt = Instant.now())
_state.value = _state.value.withSetting(key, value).copy(lastMessageAt = timeSource.now())
log(TAG) { "Setting (debounced): ${key.simpleName} = $value" }
}
}
@@ -426,7 +428,7 @@ internal class AapConnection(
prev != null && prev.primaryPod == value.secondaryPod && prev.secondaryPod == value.primaryPod
}
var newState = _state.value.withSetting(key, value).copy(lastMessageAt = Instant.now())
var newState = _state.value.withSetting(key, value).copy(lastMessageAt = timeSource.now())
if (clearPrimaryPod) {
newState = newState.copy(settings = newState.settings - AapSetting.PrimaryPod::class)
}
@@ -442,7 +444,7 @@ internal class AapConnection(
if (currentAnc != null) {
_state.value = _state.value
.withSetting(AapSetting.AncMode::class, currentAnc.copy(current = mode))
.copy(pendingAncMode = null, lastMessageAt = Instant.now())
.copy(pendingAncMode = null, lastMessageAt = timeSource.now())
} else {
_state.value = _state.value.copy(pendingAncMode = null)
}
@@ -1,6 +1,7 @@
package eu.darken.capod.pods.core.apple.aap
import android.bluetooth.BluetoothDevice
import eu.darken.capod.common.TimeSource
import eu.darken.capod.common.bluetooth.BluetoothAddress
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.debug.logging.logTag
@@ -37,6 +38,7 @@ import javax.inject.Singleton
class AapConnectionManager @Inject constructor(
private val socketFactory: L2capSocketFactory,
@AppScope private val scope: CoroutineScope,
private val timeSource: TimeSource,
) {
companion object {
private val TAG = logTag("AapConnectionMgr")
@@ -77,7 +79,7 @@ class AapConnectionManager @Inject constructor(
intentionalDisconnects.remove(address)
val profile = AapDeviceProfile.Companion.forModel(model)
val connection = AapConnection(device, profile, socketFactory)
val connection = AapConnection(device, profile, socketFactory, timeSource = timeSource)
connections[address] = connection
try {
@@ -2,6 +2,7 @@ package eu.darken.capod.pods.core.apple.ble
import android.content.Context
import androidx.annotation.DrawableRes
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.bluetooth.BluetoothAddress
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
@@ -53,7 +54,7 @@ interface BlePodSnapshot {
get() {
val sqRssi = ((rssi + 100) / 70f).coerceIn(0f, 1f)
val sqReliability = max(BASE_CONFIDENCE, reliability)
val sqAge = (Duration.between(seenFirstAt, Instant.now()).toMinutes().coerceAtMost(60) / 60f) * 0.25f
val sqAge = (Duration.between(seenFirstAt, SystemTimeSource.now()).toMinutes().coerceAtMost(60) / 60f) * 0.25f
log(VERBOSE) { "Signal Quality ($address): rssi=$sqRssi, reliability=$reliability, age=$sqAge" }
return (sqRssi + sqReliability + sqAge) / 2f
}
@@ -84,4 +85,4 @@ interface BlePodSnapshot {
companion object {
const val BASE_CONFIDENCE = 0.0f
}
}
}
@@ -1,5 +1,6 @@
package eu.darken.capod.pods.core.apple.ble.devices.airpods
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -16,8 +17,8 @@ import javax.inject.Inject
data class AirPodsGen1(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -1,5 +1,6 @@
package eu.darken.capod.pods.core.apple.ble.devices.airpods
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -16,8 +17,8 @@ import javax.inject.Inject
data class AirPodsGen2(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -2,6 +2,7 @@ package eu.darken.capod.pods.core.apple.ble.devices.airpods
import androidx.annotation.DrawableRes
import eu.darken.capod.R
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -18,8 +19,8 @@ import javax.inject.Inject
data class AirPodsGen3(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -2,6 +2,7 @@ package eu.darken.capod.pods.core.apple.ble.devices.airpods
import androidx.annotation.DrawableRes
import eu.darken.capod.R
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -18,8 +19,8 @@ import javax.inject.Inject
data class AirPodsGen4(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -2,6 +2,7 @@ package eu.darken.capod.pods.core.apple.ble.devices.airpods
import androidx.annotation.DrawableRes
import eu.darken.capod.R
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -18,8 +19,8 @@ import javax.inject.Inject
data class AirPodsGen4Anc(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -1,5 +1,6 @@
package eu.darken.capod.pods.core.apple.ble.devices.airpods
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.isBitSet
@@ -20,8 +21,8 @@ import javax.inject.Inject
data class AirPodsMax(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -1,5 +1,6 @@
package eu.darken.capod.pods.core.apple.ble.devices.airpods
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.isBitSet
@@ -20,8 +21,8 @@ import javax.inject.Inject
data class AirPodsMax2(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -1,5 +1,6 @@
package eu.darken.capod.pods.core.apple.ble.devices.airpods
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.isBitSet
@@ -20,8 +21,8 @@ import javax.inject.Inject
data class AirPodsMaxUsbc(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -2,6 +2,7 @@ package eu.darken.capod.pods.core.apple.ble.devices.airpods
import androidx.annotation.DrawableRes
import eu.darken.capod.R
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -19,8 +20,8 @@ import javax.inject.Inject
data class AirPodsPro(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -2,6 +2,7 @@ package eu.darken.capod.pods.core.apple.ble.devices.airpods
import androidx.annotation.DrawableRes
import eu.darken.capod.R
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -19,8 +20,8 @@ import javax.inject.Inject
data class AirPodsPro2(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -2,6 +2,7 @@ package eu.darken.capod.pods.core.apple.ble.devices.airpods
import androidx.annotation.DrawableRes
import eu.darken.capod.R
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -19,8 +20,8 @@ import javax.inject.Inject
data class AirPodsPro2Usbc(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -2,6 +2,7 @@ package eu.darken.capod.pods.core.apple.ble.devices.airpods
import androidx.annotation.DrawableRes
import eu.darken.capod.R
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -19,8 +20,8 @@ import javax.inject.Inject
data class AirPodsPro3(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -2,6 +2,7 @@ package eu.darken.capod.pods.core.apple.ble.devices.beats
import androidx.annotation.DrawableRes
import eu.darken.capod.R
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -18,8 +19,8 @@ import javax.inject.Inject
data class BeatsFitPro(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -1,5 +1,6 @@
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -16,8 +17,8 @@ import javax.inject.Inject
data class BeatsFlex(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -1,5 +1,6 @@
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -16,8 +17,8 @@ import javax.inject.Inject
data class BeatsSolo3(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -1,5 +1,6 @@
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -16,8 +17,8 @@ import javax.inject.Inject
data class BeatsSolo4(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -2,6 +2,7 @@ package eu.darken.capod.pods.core.apple.ble.devices.beats
import androidx.annotation.DrawableRes
import eu.darken.capod.R
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -18,8 +19,8 @@ import javax.inject.Inject
data class BeatsSoloBuds(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -1,5 +1,6 @@
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -16,8 +17,8 @@ import javax.inject.Inject
data class BeatsSoloPro(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -1,5 +1,6 @@
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -16,8 +17,8 @@ import javax.inject.Inject
data class BeatsStudio3(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -2,6 +2,7 @@ package eu.darken.capod.pods.core.apple.ble.devices.beats
import androidx.annotation.DrawableRes
import eu.darken.capod.R
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -18,8 +19,8 @@ import javax.inject.Inject
data class BeatsStudioBuds(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -2,6 +2,7 @@ package eu.darken.capod.pods.core.apple.ble.devices.beats
import androidx.annotation.DrawableRes
import eu.darken.capod.R
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -18,8 +19,8 @@ import javax.inject.Inject
data class BeatsStudioBudsPlus(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -1,5 +1,6 @@
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -16,8 +17,8 @@ import javax.inject.Inject
data class BeatsStudioPro(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -1,5 +1,6 @@
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -16,8 +17,8 @@ import javax.inject.Inject
data class BeatsX(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -1,5 +1,6 @@
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -16,8 +17,8 @@ import javax.inject.Inject
data class PowerBeats3(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -1,5 +1,6 @@
package eu.darken.capod.pods.core.apple.ble.devices.beats
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -17,8 +18,8 @@ import javax.inject.Inject
data class PowerBeats4(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -2,6 +2,7 @@ package eu.darken.capod.pods.core.apple.ble.devices.beats
import androidx.annotation.DrawableRes
import eu.darken.capod.R
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -18,8 +19,8 @@ import javax.inject.Inject
data class PowerBeatsPro(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -2,6 +2,7 @@ package eu.darken.capod.pods.core.apple.ble.devices.beats
import androidx.annotation.DrawableRes
import eu.darken.capod.R
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -18,8 +19,8 @@ import javax.inject.Inject
data class PowerBeatsPro2(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -1,5 +1,6 @@
package eu.darken.capod.pods.core.apple.ble.devices.misc
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -23,8 +24,8 @@ import javax.inject.Inject
*/
data class FakeAirPodsGen1(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -1,5 +1,6 @@
package eu.darken.capod.pods.core.apple.ble.devices.misc
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -21,8 +22,8 @@ import javax.inject.Inject
*/
data class FakeAirPodsGen2(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -2,6 +2,7 @@ package eu.darken.capod.pods.core.apple.ble.devices.misc
import androidx.annotation.DrawableRes
import eu.darken.capod.R
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -25,8 +26,8 @@ import javax.inject.Inject
*/
data class FakeAirPodsGen3(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -2,6 +2,7 @@ package eu.darken.capod.pods.core.apple.ble.devices.misc
import androidx.annotation.DrawableRes
import eu.darken.capod.R
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -25,8 +26,8 @@ import javax.inject.Inject
*/
data class FakeAirPodsPro(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -2,6 +2,7 @@ package eu.darken.capod.pods.core.apple.ble.devices.misc
import androidx.annotation.DrawableRes
import eu.darken.capod.R
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -26,8 +27,8 @@ import javax.inject.Inject
*/
data class FakeAirPodsPro2(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -2,6 +2,7 @@ package eu.darken.capod.pods.core.apple.ble.devices.misc
import android.content.Context
import eu.darken.capod.R
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -16,8 +17,8 @@ import javax.inject.Inject
data class UnknownAppleSnapshotBle(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val payload: ProximityPayload,
@@ -2,6 +2,7 @@ package eu.darken.capod.pods.core.apple.ble.history
import eu.darken.capod.common.bluetooth.BluetoothAddress
import eu.darken.capod.common.collections.median
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
import eu.darken.capod.pods.core.apple.ble.devices.ApplePods
import eu.darken.capod.pods.core.apple.ble.protocol.ProximityPayload
@@ -26,7 +27,7 @@ data class KnownDevice(
get() {
if (history.size < 2) return 0f
val now = Instant.now()
val now = SystemTimeSource.now()
val pingInterval = List(history.dropLast(1).size) { index ->
Duration.between(history[index].seenLastAt, history[index + 1].seenLastAt).toMillis()
@@ -45,7 +46,7 @@ data class KnownDevice(
}
fun rssiSmoothed(latest: Int): Int {
val now = Instant.now()
val now = SystemTimeSource.now()
return history
.filter { Duration.between(it.seenLastAt, now) < LOOKBACK }
.map { it.rssi }
@@ -54,7 +55,7 @@ data class KnownDevice(
}
fun isOlderThan(age: Duration): Boolean {
val now = Instant.now()
val now = SystemTimeSource.now()
return Duration.between(history.last().seenLastAt, now) > age
}
@@ -65,4 +66,4 @@ data class KnownDevice(
const val MAX_HISTORY = 60
val LOOKBACK = Duration.ofSeconds(30)
}
}
}
@@ -1,6 +1,7 @@
package eu.darken.capod.pods.core.unknown
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.SystemTimeSource
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
@@ -56,7 +57,7 @@ class UnknownDeviceFactory @Inject constructor() {
history.map { it.rssi }.plus(latest).takeLast(10).median()
fun isOlderThan(age: Duration): Boolean {
val now = Instant.now()
val now = SystemTimeSource.now()
return Duration.between(history.last().seenLastAt, now) > age
}
@@ -130,4 +131,4 @@ class UnknownDeviceFactory @Inject constructor() {
companion object {
private val TAG = logTag("Pod", "Unknown", "Factory")
}
}
}
@@ -2,6 +2,7 @@ package eu.darken.capod.pods.core.unknown
import android.content.Context
import eu.darken.capod.R
import eu.darken.capod.common.SystemTimeSource
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
@@ -11,8 +12,8 @@ import java.time.Instant
data class UnknownSnapshotBle(
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenLastAt: Instant = SystemTimeSource.now(),
override val seenFirstAt: Instant = SystemTimeSource.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val reliability: Float = 0f,
@@ -34,4 +35,4 @@ data class UnknownSnapshotBle(
companion object {
private val TAG = logTag("PodDevice", "Unknown")
}
}
}
@@ -1,5 +1,6 @@
package eu.darken.capod.reaction.core.popup
import eu.darken.capod.common.TimeSource
import eu.darken.capod.common.bluetooth.BluetoothAddress
import eu.darken.capod.common.bluetooth.BluetoothDevice2
import eu.darken.capod.common.bluetooth.BluetoothManager2
@@ -26,6 +27,7 @@ import javax.inject.Singleton
class PopUpReaction @Inject constructor(
private val deviceMonitor: DeviceMonitor,
private val bluetoothManager: BluetoothManager2,
private val timeSource: TimeSource,
) {
private val caseCoolDowns = java.util.concurrent.ConcurrentHashMap<String, Instant>()
@@ -45,7 +47,7 @@ class PopUpReaction @Inject constructor(
// with the toggle off). Dismiss any visible overlay immediately.
if (wasEligible && !isEligible) {
log(TAG) { "Case popup eligibility lost, emitting Hide" }
return@mapNotNull Event.PopupHide()
return@mapNotNull Event.PopupHide(timeSource.now())
}
if (!isEligible) return@mapNotNull null
@@ -71,7 +73,7 @@ class PopUpReaction @Inject constructor(
private fun throttleCasePopUps(current: PodDevice): Event? {
val cooldownKey = current.profileId ?: current.identifier?.toString() ?: return null
val now = Instant.now()
val now = timeSource.now()
val lastShown = caseCoolDowns[cooldownKey]
val decision = evaluateCasePopUp(
@@ -89,14 +91,14 @@ class PopUpReaction @Inject constructor(
return when {
decision.shouldShow -> {
caseCoolDowns[cooldownKey] = now
Event.PopupShow(device = current)
Event.PopupShow(eventAt = now, device = current)
}
decision.shouldHide -> {
if (!decision.shouldResetCooldown) {
caseCoolDowns[cooldownKey] = now
}
Event.PopupHide()
Event.PopupHide(now)
}
else -> null
@@ -142,7 +144,7 @@ class PopUpReaction @Inject constructor(
// Eligibility transition: dismiss any visible overlay immediately.
if (wasEligible && !isEligible) {
log(TAG) { "Connection popup eligibility lost, emitting Hide" }
return@mapNotNull Event.PopupHide()
return@mapNotNull Event.PopupHide(timeSource.now())
}
if (!isEligible) return@mapNotNull null
@@ -157,7 +159,7 @@ class PopUpReaction @Inject constructor(
log(TAG, VERBOSE) { "currentBroadcasted: $currentBroadcasted" }
if (previousConnected != null && previousBroadcasted != null && currentConnected == null) {
return@mapNotNull Event.PopupHide()
return@mapNotNull Event.PopupHide(timeSource.now())
}
if (currentConnected == null || currentBroadcasted == null) {
@@ -165,8 +167,9 @@ class PopUpReaction @Inject constructor(
}
val deviceSeenFirst = currentBroadcasted.seenFirstAt ?: return@mapNotNull null
val deviceAge = Duration.between(deviceSeenFirst, Instant.now())
val connectionAge = Duration.between(currentConnected.seenFirstAt, Instant.now())
val now = timeSource.now()
val deviceAge = Duration.between(deviceSeenFirst, now)
val connectionAge = Duration.between(currentConnected.seenFirstAt, now)
val decision = evaluateConnectionPopUp(
hasConnectedDevice = true,
@@ -179,8 +182,8 @@ class PopUpReaction @Inject constructor(
log(TAG) { "Connection popup decision: ${decision.reason}" }
if (decision.shouldShow) {
connectionCoolDowns[currentConnected.address] = Instant.now()
Event.PopupShow(device = currentBroadcasted)
connectionCoolDowns[currentConnected.address] = now
Event.PopupShow(eventAt = now, device = currentBroadcasted)
} else {
null
}
@@ -191,12 +194,12 @@ class PopUpReaction @Inject constructor(
sealed class Event {
data class PopupShow(
val eventAt: Instant = Instant.now(),
val eventAt: Instant,
val device: PodDevice,
) : Event()
data class PopupHide(
val eventAt: Instant = Instant.now(),
val eventAt: Instant,
) : Event()
}
@@ -4,6 +4,7 @@ import android.content.Context
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import eu.darken.capod.R
import eu.darken.capod.common.TimeSource
import eu.darken.capod.common.bluetooth.ScannerMode
import eu.darken.capod.common.coroutine.DispatcherProvider
import eu.darken.capod.common.datastore.valueBlocking
@@ -41,6 +42,7 @@ class TroubleShooterViewModel @Inject constructor(
private val blePodMonitor: BlePodMonitor,
private val deviceMonitor: DeviceMonitor,
private val debugSettings: DebugSettings,
private val timeSource: TimeSource,
) : ViewModel4(dispatcherProvider) {
private val _bleState = MutableStateFlow<BleState>(BleState.Intro())
@@ -113,11 +115,11 @@ class TroubleShooterViewModel @Inject constructor(
generalSettings.useIndirectScanResultCallback.valueBlocking = indirectCallback
debugSettings.showUnfiltered.valueBlocking = unfiltered
val start = System.currentTimeMillis()
val start = timeSource.elapsedRealtime()
val devices = withTimeoutOrNull(STEP_TIME) {
blePodMonitor.devices
.take(10)
.takeWhile { System.currentTimeMillis() - start < STEP_TIME - 1000 }
.takeWhile { timeSource.elapsedRealtime() - start < STEP_TIME - 1000 }
.toList()
.flatten()
.distinctBy { it.address }
@@ -195,10 +197,10 @@ class TroubleShooterViewModel @Inject constructor(
progress("Checking all closeby headphones.")
val otherDevices = withTimeoutOrNull(STEP_TIME) {
val start = System.currentTimeMillis()
val start = timeSource.elapsedRealtime()
blePodMonitor.devices
.take(10)
.takeWhile { System.currentTimeMillis() - start < STEP_TIME - 1000 }
.takeWhile { timeSource.elapsedRealtime() - start < STEP_TIME - 1000 }
.toList()
.flatten()
.distinctBy { it.address }