From 1cd8ae5ace8e83210cd432cc5bbc75d25d5fcf94 Mon Sep 17 00:00:00 2001 From: darken Date: Mon, 13 Apr 2026 16:53:40 +0200 Subject: [PATCH] Refactor time access behind TimeSource --- .../eu/darken/capod/common/MediaControl.kt | 8 ++--- .../eu/darken/capod/common/SystemClockWrap.kt | 9 ----- .../java/eu/darken/capod/common/TimeModule.kt | 14 ++++++++ .../java/eu/darken/capod/common/TimeSource.kt | 29 +++++++++++++++ .../capod/common/bluetooth/BleScanResult.kt | 11 ++++-- .../capod/common/bluetooth/BleScanner.kt | 16 +++++---- .../common/bluetooth/BluetoothManager2.kt | 16 ++++++--- .../capod/common/bluetooth/FakeBleData.kt | 36 ++++++++++--------- .../capod/common/debug/logging/FileLogger.kt | 9 +++-- .../common/debug/recording/core/Recorder.kt | 9 +++-- .../debug/recording/core/RecorderModule.kt | 15 ++++---- .../devicesettings/DeviceSettingsViewModel.kt | 7 ++-- .../capod/main/ui/overview/OverviewScreen.kt | 9 ++--- .../main/ui/overview/OverviewViewModel.kt | 4 ++- .../main/ui/overview/cards/DualPodsCard.kt | 9 ++--- .../main/ui/overview/cards/SinglePodsCard.kt | 9 ++--- .../main/ui/settings/support/SupportScreen.kt | 3 +- .../support/contactform/ContactFormScreen.kt | 3 +- .../capod/monitor/core/DeviceMonitor.kt | 4 ++- .../eu/darken/capod/monitor/core/PodDevice.kt | 3 +- .../capod/monitor/core/ble/BlePodMonitor.kt | 8 +++-- .../core/cache/DeviceStateCacheExtensions.kt | 3 +- .../pods/core/apple/aap/AapConnection.kt | 26 +++++++------- .../core/apple/aap/AapConnectionManager.kt | 4 ++- .../pods/core/apple/ble/BlePodSnapshot.kt | 5 +-- .../apple/ble/devices/airpods/AirPodsGen1.kt | 5 +-- .../apple/ble/devices/airpods/AirPodsGen2.kt | 5 +-- .../apple/ble/devices/airpods/AirPodsGen3.kt | 5 +-- .../apple/ble/devices/airpods/AirPodsGen4.kt | 5 +-- .../ble/devices/airpods/AirPodsGen4Anc.kt | 5 +-- .../apple/ble/devices/airpods/AirPodsMax.kt | 5 +-- .../apple/ble/devices/airpods/AirPodsMax2.kt | 5 +-- .../ble/devices/airpods/AirPodsMaxUsbc.kt | 5 +-- .../apple/ble/devices/airpods/AirPodsPro.kt | 5 +-- .../apple/ble/devices/airpods/AirPodsPro2.kt | 5 +-- .../ble/devices/airpods/AirPodsPro2Usbc.kt | 5 +-- .../apple/ble/devices/airpods/AirPodsPro3.kt | 5 +-- .../apple/ble/devices/beats/BeatsFitPro.kt | 5 +-- .../core/apple/ble/devices/beats/BeatsFlex.kt | 5 +-- .../apple/ble/devices/beats/BeatsSolo3.kt | 5 +-- .../apple/ble/devices/beats/BeatsSolo4.kt | 5 +-- .../apple/ble/devices/beats/BeatsSoloBuds.kt | 5 +-- .../apple/ble/devices/beats/BeatsSoloPro.kt | 5 +-- .../apple/ble/devices/beats/BeatsStudio3.kt | 5 +-- .../ble/devices/beats/BeatsStudioBuds.kt | 5 +-- .../ble/devices/beats/BeatsStudioBudsPlus.kt | 5 +-- .../apple/ble/devices/beats/BeatsStudioPro.kt | 5 +-- .../core/apple/ble/devices/beats/BeatsX.kt | 5 +-- .../apple/ble/devices/beats/PowerBeats3.kt | 5 +-- .../apple/ble/devices/beats/PowerBeats4.kt | 5 +-- .../apple/ble/devices/beats/PowerBeatsPro.kt | 5 +-- .../apple/ble/devices/beats/PowerBeatsPro2.kt | 5 +-- .../apple/ble/devices/misc/FakeAirPodsGen1.kt | 5 +-- .../apple/ble/devices/misc/FakeAirPodsGen2.kt | 5 +-- .../apple/ble/devices/misc/FakeAirPodsGen3.kt | 5 +-- .../apple/ble/devices/misc/FakeAirPodsPro.kt | 5 +-- .../apple/ble/devices/misc/FakeAirPodsPro2.kt | 5 +-- .../devices/misc/UnknownAppleSnapshotBle.kt | 5 +-- .../core/apple/ble/history/KnownDevice.kt | 9 ++--- .../pods/core/unknown/UnknownDeviceFactory.kt | 5 +-- .../pods/core/unknown/UnknownSnapshotBle.kt | 7 ++-- .../reaction/core/popup/PopUpReaction.kt | 27 +++++++------- .../ui/TroubleShooterViewModel.kt | 10 +++--- .../darken/capod/common/MediaControlTest.kt | 20 ++++------- .../DeviceSettingsViewModelTest.kt | 4 +++ .../main/ui/overview/OverviewViewModelTest.kt | 4 +++ .../capod/monitor/core/DeviceMonitorTest.kt | 4 +++ .../apple/aap/AapConnectionManagerTest.kt | 4 +++ .../core/apple/ble/devices/BaseBlePodsTest.kt | 14 +------- .../core/popup/PopUpReactionLogicTest.kt | 2 ++ .../test/java/testhelpers/TestTimeSource.kt | 30 ++++++++++++++++ 71 files changed, 362 insertions(+), 212 deletions(-) delete mode 100644 app/src/main/java/eu/darken/capod/common/SystemClockWrap.kt create mode 100644 app/src/main/java/eu/darken/capod/common/TimeModule.kt create mode 100644 app/src/main/java/eu/darken/capod/common/TimeSource.kt create mode 100644 app/src/test/java/testhelpers/TestTimeSource.kt diff --git a/app/src/main/java/eu/darken/capod/common/MediaControl.kt b/app/src/main/java/eu/darken/capod/common/MediaControl.kt index 33e5222e..0055ead8 100644 --- a/app/src/main/java/eu/darken/capod/common/MediaControl.kt +++ b/app/src/main/java/eu/darken/capod/common/MediaControl.kt @@ -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() { diff --git a/app/src/main/java/eu/darken/capod/common/SystemClockWrap.kt b/app/src/main/java/eu/darken/capod/common/SystemClockWrap.kt deleted file mode 100644 index 8c402e78..00000000 --- a/app/src/main/java/eu/darken/capod/common/SystemClockWrap.kt +++ /dev/null @@ -1,9 +0,0 @@ -package eu.darken.capod.common - -import android.os.SystemClock - -object SystemClockWrap { - - val elapsedRealtimeNanos: Long - get() = SystemClock.elapsedRealtimeNanos() -} \ No newline at end of file diff --git a/app/src/main/java/eu/darken/capod/common/TimeModule.kt b/app/src/main/java/eu/darken/capod/common/TimeModule.kt new file mode 100644 index 00000000..051c2345 --- /dev/null +++ b/app/src/main/java/eu/darken/capod/common/TimeModule.kt @@ -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 +} diff --git a/app/src/main/java/eu/darken/capod/common/TimeSource.kt b/app/src/main/java/eu/darken/capod/common/TimeSource.kt new file mode 100644 index 00000000..871f07ed --- /dev/null +++ b/app/src/main/java/eu/darken/capod/common/TimeSource.kt @@ -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 diff --git a/app/src/main/java/eu/darken/capod/common/bluetooth/BleScanResult.kt b/app/src/main/java/eu/darken/capod/common/bluetooth/BleScanResult.kt index 09f7f13d..5f6f57d4 100644 --- a/app/src/main/java/eu/darken/capod/common/bluetooth/BleScanResult.kt +++ b/app/src/main/java/eu/darken/capod/common/bluetooth/BleScanResult.kt @@ -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( } ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/darken/capod/common/bluetooth/BleScanner.kt b/app/src/main/java/eu/darken/capod/common/bluetooth/BleScanner.kt index 8c9afb55..a08cdf27 100644 --- a/app/src/main/java/eu/darken/capod/common/bluetooth/BleScanner.kt +++ b/app/src/main/java/eu/darken/capod/common/bluetooth/BleScanner.kt @@ -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) { 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") } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/darken/capod/common/bluetooth/BluetoothManager2.kt b/app/src/main/java/eu/darken/capod/common/bluetooth/BluetoothManager2.kt index 2c7c0c2e..dcb51333 100644 --- a/app/src/main/java/eu/darken/capod/common/bluetooth/BluetoothManager2.kt +++ b/app/src/main/java/eu/darken/capod/common/bluetooth/BluetoothManager2.kt @@ -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") } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/darken/capod/common/bluetooth/FakeBleData.kt b/app/src/main/java/eu/darken/capod/common/bluetooth/FakeBleData.kt index 48cbc006..393d8ecc 100644 --- a/app/src/main/java/eu/darken/capod/common/bluetooth/FakeBleData.kt +++ b/app/src/main/java/eu/darken/capod/common/bluetooth/FakeBleData.kt @@ -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): Collection { @@ -21,12 +21,14 @@ class FakeBleData @Inject constructor( fun getFakeData(): Collection { val fakeDevices = mutableListOf() + 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 } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/darken/capod/common/debug/logging/FileLogger.kt b/app/src/main/java/eu/darken/capod/common/debug/logging/FileLogger.kt index b9a408e8..70e659ae 100644 --- a/app/src/main/java/eu/darken/capod/common/debug/logging/FileLogger.kt +++ b/app/src/main/java/eu/darken/capod/common/debug/logging/FileLogger.kt @@ -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?) { 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") } } - diff --git a/app/src/main/java/eu/darken/capod/common/debug/recording/core/Recorder.kt b/app/src/main/java/eu/darken/capod/common/debug/recording/core/Recorder.kt index b056be60..84dcb311 100644 --- a/app/src/main/java/eu/darken/capod/common/debug/recording/core/Recorder.kt +++ b/app/src/main/java/eu/darken/capod/common/debug/recording/core/Recorder.kt @@ -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") } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/darken/capod/common/debug/recording/core/RecorderModule.kt b/app/src/main/java/eu/darken/capod/common/debug/recording/core/RecorderModule.kt index 67aa6c0a..754b2f20 100644 --- a/app/src/main/java/eu/darken/capod/common/debug/recording/core/RecorderModule.kt +++ b/app/src/main/java/eu/darken/capod/common/debug/recording/core/RecorderModule.kt @@ -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? { val trimmed = content.trim() if (trimmed.isEmpty()) return null diff --git a/app/src/main/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsViewModel.kt b/app/src/main/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsViewModel.kt index 08dc3533..ca39fb12 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsViewModel.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsViewModel.kt @@ -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(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, 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 c7908b47..551d1fc8 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 @@ -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, 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 18086ecd..927b5608 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 @@ -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() @@ -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, 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 bea0c443..f91f44ac 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 @@ -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 = {}, ) } 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 e6fac054..e8925453 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 @@ -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 = {}, ) } diff --git a/app/src/main/java/eu/darken/capod/main/ui/settings/support/SupportScreen.kt b/app/src/main/java/eu/darken/capod/main/ui/settings/support/SupportScreen.kt index 20e1ee28..41c8f37e 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/settings/support/SupportScreen.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/settings/support/SupportScreen.kt @@ -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, ) diff --git a/app/src/main/java/eu/darken/capod/main/ui/settings/support/contactform/ContactFormScreen.kt b/app/src/main/java/eu/darken/capod/main/ui/settings/support/contactform/ContactFormScreen.kt index dfe32d14..275e2764 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/settings/support/contactform/ContactFormScreen.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/settings/support/contactform/ContactFormScreen.kt @@ -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, ) 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 d5332740..0abfe9af 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 @@ -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) diff --git a/app/src/main/java/eu/darken/capod/monitor/core/PodDevice.kt b/app/src/main/java/eu/darken/capod/monitor/core/PodDevice.kt index fdf3b2df..e7604cea 100644 --- a/app/src/main/java/eu/darken/capod/monitor/core/PodDevice.kt +++ b/app/src/main/java/eu/darken/capod/monitor/core/PodDevice.kt @@ -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) } /** diff --git a/app/src/main/java/eu/darken/capod/monitor/core/ble/BlePodMonitor.kt b/app/src/main/java/eu/darken/capod/monitor/core/ble/BlePodMonitor.kt index 58532147..6b8adfba 100644 --- a/app/src/main/java/eu/darken/capod/monitor/core/ble/BlePodMonitor.kt +++ b/app/src/main/java/eu/darken/capod/monitor/core/ble/BlePodMonitor.kt @@ -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): List { - 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) } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/darken/capod/monitor/core/cache/DeviceStateCacheExtensions.kt b/app/src/main/java/eu/darken/capod/monitor/core/cache/DeviceStateCacheExtensions.kt index 08121fcf..e5337e06 100644 --- a/app/src/main/java/eu/darken/capod/monitor/core/cache/DeviceStateCacheExtensions.kt +++ b/app/src/main/java/eu/darken/capod/monitor/core/cache/DeviceStateCacheExtensions.kt @@ -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 diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/aap/AapConnection.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/aap/AapConnection.kt index ef2ac604..4a444fe6 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/aap/AapConnection.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/aap/AapConnection.kt @@ -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() == 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) } diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/aap/AapConnectionManager.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/aap/AapConnectionManager.kt index a3a176cb..e02af01c 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/aap/AapConnectionManager.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/aap/AapConnectionManager.kt @@ -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 { diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/BlePodSnapshot.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/BlePodSnapshot.kt index 5b2815cf..0be97c64 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/BlePodSnapshot.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/BlePodSnapshot.kt @@ -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 } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsGen1.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsGen1.kt index 52c214a4..47123a4d 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsGen1.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsGen1.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsGen2.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsGen2.kt index 82746db7..a5efc34e 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsGen2.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsGen2.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsGen3.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsGen3.kt index 7ef94c16..2d342606 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsGen3.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsGen3.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsGen4.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsGen4.kt index ea5c72d3..e6ab0071 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsGen4.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsGen4.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsGen4Anc.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsGen4Anc.kt index e71f38cb..988f4271 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsGen4Anc.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsGen4Anc.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsMax.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsMax.kt index c8c902c7..c34e85b6 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsMax.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsMax.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsMax2.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsMax2.kt index ea87c5fd..d28f063d 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsMax2.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsMax2.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsMaxUsbc.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsMaxUsbc.kt index d66a07e9..339a86f1 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsMaxUsbc.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsMaxUsbc.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsPro.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsPro.kt index c851464c..61b0c9d0 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsPro.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsPro.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsPro2.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsPro2.kt index ec1ba28d..2ea7288c 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsPro2.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsPro2.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsPro2Usbc.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsPro2Usbc.kt index bbf04f61..79851b08 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsPro2Usbc.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsPro2Usbc.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsPro3.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsPro3.kt index 9eb711bd..ba7148b7 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsPro3.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsPro3.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsFitPro.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsFitPro.kt index 7ba1c8e4..b7c63ddd 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsFitPro.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsFitPro.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsFlex.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsFlex.kt index 91b1ff22..8acd908d 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsFlex.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsFlex.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsSolo3.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsSolo3.kt index 70abf1d1..0bd35f1b 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsSolo3.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsSolo3.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsSolo4.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsSolo4.kt index 9be53a1f..c0a3a0f9 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsSolo4.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsSolo4.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsSoloBuds.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsSoloBuds.kt index 52bf80f0..940f77ce 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsSoloBuds.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsSoloBuds.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsSoloPro.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsSoloPro.kt index 0b8e2310..3c9dd3be 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsSoloPro.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsSoloPro.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsStudio3.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsStudio3.kt index e81c3a66..a6ac58f1 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsStudio3.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsStudio3.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsStudioBuds.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsStudioBuds.kt index 61f2b507..5c24b9f5 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsStudioBuds.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsStudioBuds.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsStudioBudsPlus.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsStudioBudsPlus.kt index ce7b9eb6..3440cad9 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsStudioBudsPlus.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsStudioBudsPlus.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsStudioPro.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsStudioPro.kt index 825e74ae..ba10614e 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsStudioPro.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsStudioPro.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsX.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsX.kt index 63f366da..43e9010a 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsX.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/BeatsX.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/PowerBeats3.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/PowerBeats3.kt index 55a469f5..13a34a07 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/PowerBeats3.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/PowerBeats3.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/PowerBeats4.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/PowerBeats4.kt index c337f32f..754adf40 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/PowerBeats4.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/PowerBeats4.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/PowerBeatsPro.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/PowerBeatsPro.kt index 5096bc92..bb427167 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/PowerBeatsPro.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/PowerBeatsPro.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/PowerBeatsPro2.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/PowerBeatsPro2.kt index fd56ce8c..b4fb03c7 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/PowerBeatsPro2.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/beats/PowerBeatsPro2.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/misc/FakeAirPodsGen1.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/misc/FakeAirPodsGen1.kt index 48516241..a1397190 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/misc/FakeAirPodsGen1.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/misc/FakeAirPodsGen1.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/misc/FakeAirPodsGen2.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/misc/FakeAirPodsGen2.kt index ea6b667d..d16eff3d 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/misc/FakeAirPodsGen2.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/misc/FakeAirPodsGen2.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/misc/FakeAirPodsGen3.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/misc/FakeAirPodsGen3.kt index baf48f37..721b2002 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/misc/FakeAirPodsGen3.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/misc/FakeAirPodsGen3.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/misc/FakeAirPodsPro.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/misc/FakeAirPodsPro.kt index 77e22a9f..da1d6f46 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/misc/FakeAirPodsPro.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/misc/FakeAirPodsPro.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/misc/FakeAirPodsPro2.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/misc/FakeAirPodsPro2.kt index 994be3c0..de4fa109 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/misc/FakeAirPodsPro2.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/misc/FakeAirPodsPro2.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/misc/UnknownAppleSnapshotBle.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/misc/UnknownAppleSnapshotBle.kt index a87381bf..7fd50e74 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/misc/UnknownAppleSnapshotBle.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/devices/misc/UnknownAppleSnapshotBle.kt @@ -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, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/history/KnownDevice.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/history/KnownDevice.kt index 87db7f35..62d35754 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/history/KnownDevice.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/history/KnownDevice.kt @@ -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) } -} \ 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 6719ea1b..da6e1dee 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 @@ -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") } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/darken/capod/pods/core/unknown/UnknownSnapshotBle.kt b/app/src/main/java/eu/darken/capod/pods/core/unknown/UnknownSnapshotBle.kt index 69c8cc53..c0aa449f 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/unknown/UnknownSnapshotBle.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/unknown/UnknownSnapshotBle.kt @@ -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") } -} \ No newline at end of file +} 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 83d315fc..27af0e00 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 @@ -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() @@ -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() } 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 09b0600a..f80ef3f4 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 @@ -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.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 } diff --git a/app/src/test/java/eu/darken/capod/common/MediaControlTest.kt b/app/src/test/java/eu/darken/capod/common/MediaControlTest.kt index 526b6df3..2069f46c 100644 --- a/app/src/test/java/eu/darken/capod/common/MediaControlTest.kt +++ b/app/src/test/java/eu/darken/capod/common/MediaControlTest.kt @@ -1,41 +1,35 @@ package eu.darken.capod.common import android.media.AudioManager -import android.os.SystemClock import io.mockk.Runs import io.mockk.clearMocks import io.mockk.every import io.mockk.just import io.mockk.mockk -import io.mockk.mockkStatic -import io.mockk.unmockkStatic import io.mockk.verify -import org.junit.jupiter.api.AfterEach import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest +import testhelpers.TestTimeSource class MediaControlTest : BaseTest() { private lateinit var audioManager: AudioManager private lateinit var mediaControl: MediaControl + private lateinit var timeSource: TestTimeSource @BeforeEach fun setup() { - mockkStatic(SystemClock::class) - every { SystemClock.elapsedRealtime() } returns 1_000L - every { SystemClock.uptimeMillis() } returns 1_000L + timeSource = TestTimeSource( + elapsedRealtimeMs = 1_000L, + uptimeMillisValue = 1_000L, + ) audioManager = mockk(relaxed = true) every { audioManager.dispatchMediaKeyEvent(any()) } just Runs - mediaControl = MediaControl(audioManager) - } - - @AfterEach - fun teardown() { - unmockkStatic(SystemClock::class) + mediaControl = MediaControl(audioManager, timeSource) } @Test diff --git a/app/src/test/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsViewModelTest.kt b/app/src/test/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsViewModelTest.kt index 48551393..b96090ac 100644 --- a/app/src/test/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsViewModelTest.kt +++ b/app/src/test/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsViewModelTest.kt @@ -1,5 +1,6 @@ package eu.darken.capod.main.ui.devicesettings +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 @@ -36,6 +37,7 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import testhelpers.BaseTest +import testhelpers.TestTimeSource import testhelpers.coroutine.TestDispatcherProvider import testhelpers.livedata.InstantExecutorExtension @@ -53,6 +55,7 @@ class DeviceSettingsViewModelTest : BaseTest() { private lateinit var bluetoothManager: BluetoothManager2 private lateinit var profilesRepo: DeviceProfilesRepo private lateinit var generalSettings: GeneralSettings + private val timeSource: TimeSource = TestTimeSource() private lateinit var devicesFlow: MutableStateFlow> private lateinit var upgradeInfoFlow: MutableStateFlow @@ -107,6 +110,7 @@ class DeviceSettingsViewModelTest : BaseTest() { bluetoothManager = bluetoothManager, profilesRepo = profilesRepo, generalSettings = generalSettings, + timeSource = timeSource, ) @Test 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 c4ee24f6..ccfbc7e4 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 @@ -1,5 +1,6 @@ package eu.darken.capod.main.ui.overview +import eu.darken.capod.common.TimeSource import eu.darken.capod.common.bluetooth.BluetoothDevice2 import eu.darken.capod.common.bluetooth.BluetoothManager2 import eu.darken.capod.common.debug.DebugSettings @@ -34,6 +35,7 @@ import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import testhelpers.BaseTest +import testhelpers.TestTimeSource import testhelpers.coroutine.TestDispatcherProvider import testhelpers.datastore.FakeDataStoreValue import testhelpers.livedata.InstantExecutorExtension @@ -52,6 +54,7 @@ class OverviewViewModelTest : BaseTest() { private lateinit var upgradeRepo: UpgradeRepo private lateinit var bluetoothManager: BluetoothManager2 private lateinit var profilesRepo: DeviceProfilesRepo + private val timeSource: TimeSource = TestTimeSource() private lateinit var missingPermissionsFlow: MutableStateFlow> private lateinit var devicesFlow: MutableStateFlow> @@ -126,6 +129,7 @@ class OverviewViewModelTest : BaseTest() { bluetoothManager = bluetoothManager, profilesRepo = profilesRepo, aapManager = mockk(relaxed = true), + timeSource = timeSource, ) @Nested diff --git a/app/src/test/java/eu/darken/capod/monitor/core/DeviceMonitorTest.kt b/app/src/test/java/eu/darken/capod/monitor/core/DeviceMonitorTest.kt index 58610d3f..e088d9a6 100644 --- a/app/src/test/java/eu/darken/capod/monitor/core/DeviceMonitorTest.kt +++ b/app/src/test/java/eu/darken/capod/monitor/core/DeviceMonitorTest.kt @@ -1,5 +1,6 @@ package eu.darken.capod.monitor.core +import eu.darken.capod.common.TimeSource import eu.darken.capod.common.bluetooth.BluetoothAddress import eu.darken.capod.monitor.core.aap.AapLifecycleManager import eu.darken.capod.monitor.core.ble.BlePodMonitor @@ -26,12 +27,14 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Test import testhelpers.BaseTest +import testhelpers.TestTimeSource import java.time.Instant @OptIn(ExperimentalCoroutinesApi::class) class DeviceMonitorTest : BaseTest() { private val testDispatcher = UnconfinedTestDispatcher() + private val timeSource: TimeSource = TestTimeSource(wallNow = Instant.parse("2026-04-05T18:00:00Z")) private val testAddress: BluetoothAddress = "AA:BB:CC:DD:EE:FF" private val testProfile = AppleDeviceProfile( @@ -135,6 +138,7 @@ class DeviceMonitorTest : BaseTest() { deviceStateCache = deviceStateCache, profilesRepo = profilesRepo, aapLifecycleManager = aapLifecycleManager, + timeSource = timeSource, ) } diff --git a/app/src/test/java/eu/darken/capod/pods/core/apple/aap/AapConnectionManagerTest.kt b/app/src/test/java/eu/darken/capod/pods/core/apple/aap/AapConnectionManagerTest.kt index 7f2e6d44..e16f07c8 100644 --- a/app/src/test/java/eu/darken/capod/pods/core/apple/aap/AapConnectionManagerTest.kt +++ b/app/src/test/java/eu/darken/capod/pods/core/apple/aap/AapConnectionManagerTest.kt @@ -2,6 +2,7 @@ package eu.darken.capod.pods.core.apple.aap 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.pods.core.apple.PodModel import eu.darken.capod.pods.core.apple.aap.protocol.AapCommand @@ -20,6 +21,7 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import testhelpers.BaseTest +import testhelpers.TestTimeSource import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.IOException @@ -32,6 +34,7 @@ class AapConnectionManagerTest : BaseTest() { private lateinit var socketFactory: L2capSocketFactory private lateinit var manager: AapConnectionManager + private val timeSource: TimeSource = TestTimeSource() private val testAddress = "AA:BB:CC:DD:EE:FF" private val testDevice: BluetoothDevice = mockk(relaxed = true) { @@ -44,6 +47,7 @@ class AapConnectionManagerTest : BaseTest() { manager = AapConnectionManager( socketFactory = socketFactory, scope = testScope, + timeSource = timeSource, ) } diff --git a/app/src/test/java/eu/darken/capod/pods/core/apple/ble/devices/BaseBlePodsTest.kt b/app/src/test/java/eu/darken/capod/pods/core/apple/ble/devices/BaseBlePodsTest.kt index 18059777..20de2ffd 100644 --- a/app/src/test/java/eu/darken/capod/pods/core/apple/ble/devices/BaseBlePodsTest.kt +++ b/app/src/test/java/eu/darken/capod/pods/core/apple/ble/devices/BaseBlePodsTest.kt @@ -2,7 +2,6 @@ package eu.darken.capod.pods.core.apple.ble.devices import dagger.BindsInstance import dagger.Component -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 @@ -15,9 +14,6 @@ import eu.darken.capod.profiles.core.DeviceProfilesRepo import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.mockk -import io.mockk.mockkObject -import io.mockk.unmockkObject -import org.junit.jupiter.api.AfterEach import kotlinx.coroutines.flow.flowOf import org.junit.jupiter.api.BeforeEach import testhelpers.BaseTest @@ -59,14 +55,6 @@ abstract class BaseBlePodsTest : BaseTest() { @BeforeEach fun setup() { MockKAnnotations.init(this) - - mockkObject(SystemClockWrap) - every { SystemClockWrap.elapsedRealtimeNanos } returns 1000L - } - - @AfterEach - fun teardown() { - unmockkObject(SystemClockWrap) } internal suspend inline fun create( @@ -75,7 +63,7 @@ abstract class BaseBlePodsTest : BaseTest() { block: T.() -> Unit ) { val result = BleScanResult( - receivedAt = Instant.now(), + receivedAt = Instant.parse("2026-01-01T00:00:00Z"), address = address, rssi = -66, generatedAtNanos = 136136027721826, diff --git a/app/src/test/java/eu/darken/capod/reaction/core/popup/PopUpReactionLogicTest.kt b/app/src/test/java/eu/darken/capod/reaction/core/popup/PopUpReactionLogicTest.kt index 71e56ad6..19724cdd 100644 --- a/app/src/test/java/eu/darken/capod/reaction/core/popup/PopUpReactionLogicTest.kt +++ b/app/src/test/java/eu/darken/capod/reaction/core/popup/PopUpReactionLogicTest.kt @@ -7,6 +7,7 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import testhelpers.BaseTest +import testhelpers.TestTimeSource import java.time.Duration import java.time.Instant @@ -19,6 +20,7 @@ class PopUpReactionLogicTest : BaseTest() { popUpReaction = PopUpReaction( deviceMonitor = mockk(relaxed = true), bluetoothManager = mockk(relaxed = true), + timeSource = TestTimeSource(), ) } diff --git a/app/src/test/java/testhelpers/TestTimeSource.kt b/app/src/test/java/testhelpers/TestTimeSource.kt new file mode 100644 index 00000000..0f7cf008 --- /dev/null +++ b/app/src/test/java/testhelpers/TestTimeSource.kt @@ -0,0 +1,30 @@ +package testhelpers + +import eu.darken.capod.common.TimeSource +import java.time.Duration +import java.time.Instant + +class TestTimeSource( + var wallNow: Instant = Instant.parse("2026-01-01T00:00:00Z"), + var elapsedRealtimeMs: Long = 0L, + var elapsedRealtimeNanosValue: Long = 0L, + var uptimeMillisValue: Long = 0L, +) : TimeSource { + + override fun now(): Instant = wallNow + + override fun currentTimeMillis(): Long = wallNow.toEpochMilli() + + override fun elapsedRealtime(): Long = elapsedRealtimeMs + + override fun elapsedRealtimeNanos(): Long = elapsedRealtimeNanosValue + + override fun uptimeMillis(): Long = uptimeMillisValue + + fun advanceBy(duration: Duration) { + wallNow = wallNow.plus(duration) + elapsedRealtimeMs += duration.toMillis() + elapsedRealtimeNanosValue += duration.toNanos() + uptimeMillisValue += duration.toMillis() + } +}