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