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