diff --git a/app-common/src/main/java/eu/darken/capod/common/bluetooth/BleScanResult.kt b/app-common/src/main/java/eu/darken/capod/common/bluetooth/BleScanResult.kt index 4bacb273..b6e0d44b 100644 --- a/app-common/src/main/java/eu/darken/capod/common/bluetooth/BleScanResult.kt +++ b/app-common/src/main/java/eu/darken/capod/common/bluetooth/BleScanResult.kt @@ -3,14 +3,17 @@ package eu.darken.capod.common.bluetooth import android.bluetooth.le.ScanResult import android.os.Parcelable import androidx.core.util.forEach +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass import kotlinx.parcelize.Parcelize @Parcelize +@JsonClass(generateAdapter = true) data class BleScanResult( - val address: String, - val rssi: Int, - val generatedAtNanos: Long, - val manufacturerSpecificData: Map + @Json(name = "address") val address: String, + @Json(name = "rssi") val rssi: Int, + @Json(name = "generatedAtNanos") val generatedAtNanos: Long, + @Json(name = "manufacturerSpecificData") val manufacturerSpecificData: Map ) : Parcelable { fun getManufacturerSpecificData(id: Int): ByteArray? = manufacturerSpecificData[id] diff --git a/app-common/src/main/java/eu/darken/capod/monitor/core/PodDeviceCache.kt b/app-common/src/main/java/eu/darken/capod/monitor/core/PodDeviceCache.kt new file mode 100644 index 00000000..da4269bf --- /dev/null +++ b/app-common/src/main/java/eu/darken/capod/monitor/core/PodDeviceCache.kt @@ -0,0 +1,68 @@ +package eu.darken.capod.monitor.core + +import android.content.Context +import com.squareup.moshi.Moshi +import com.squareup.moshi.adapter +import dagger.hilt.android.qualifiers.ApplicationContext +import eu.darken.capod.common.bluetooth.BleScanResult +import eu.darken.capod.common.coroutine.DispatcherProvider +import eu.darken.capod.common.debug.logging.Logging.Priority.ERROR +import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE +import eu.darken.capod.common.debug.logging.asLog +import eu.darken.capod.common.debug.logging.log +import eu.darken.capod.common.debug.logging.logTag +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext +import java.io.File +import javax.inject.Inject +import javax.inject.Singleton + +@Suppress("BlockingMethodInNonBlockingContext") +@Singleton +class PodDeviceCache @Inject constructor( + @ApplicationContext private val context: Context, + private val dispatcherProvider: DispatcherProvider, + moshi: Moshi, +) { + private val cacheDir by lazy { + File(context.cacheDir, "device_cache").apply { mkdirs() } + } + private val mainDeviceCacheFile = File(cacheDir, "main_device.raw") + private val jsonAdapter = moshi.adapter() + private val lock = Mutex() + + suspend fun saveMainDevice(device: BleScanResult?) = withContext(dispatcherProvider.IO) { + log(TAG, VERBOSE) { "saveMainDevice(device=$device)" } + lock.withLock { + try { + if (device == null) { + mainDeviceCacheFile.delete() + } else { + val json = jsonAdapter.toJson(device) + mainDeviceCacheFile.writeText(json) + } + } catch (e: Exception) { + log(TAG, ERROR) { "Failed to save $device:${e.asLog()}" } + } + } + } + + suspend fun loadMainDevice(): BleScanResult? = withContext(dispatcherProvider.IO) { + log(TAG) { "loadMainDevice()" } + lock.withLock { + try { + val raw = mainDeviceCacheFile.readText() + jsonAdapter.fromJson(raw) + } catch (e: Exception) { + log(TAG, ERROR) { "Failed to read main-device:${e.asLog()}" } + null + } + } + } + + companion object { + private val TAG = logTag("Monitor", "PodMonitor", "Cache") + } +} + diff --git a/app-common/src/main/java/eu/darken/capod/monitor/core/PodMonitor.kt b/app-common/src/main/java/eu/darken/capod/monitor/core/PodMonitor.kt index 6e1f571f..706720c1 100644 --- a/app-common/src/main/java/eu/darken/capod/monitor/core/PodMonitor.kt +++ b/app-common/src/main/java/eu/darken/capod/monitor/core/PodMonitor.kt @@ -29,12 +29,13 @@ import javax.inject.Singleton @Singleton class PodMonitor @Inject constructor( + @AppScope private val appScope: CoroutineScope, private val bleScanner: BleScanner, private val podFactory: PodFactory, private val generalSettings: GeneralSettings, private val bluetoothManager: BluetoothManager2, private val debugSettings: DebugSettings, - @AppScope private val appScope: CoroutineScope + private val podDeviceCache: PodDeviceCache, ) { private val deviceCache = mutableMapOf() @@ -42,7 +43,7 @@ class PodMonitor @Inject constructor( private suspend fun List.preFilterAndMap( scannerMode: ScannerMode - ): List = this + ): List = this .groupBy { it.address } .values .map { sameAdrDevs -> @@ -103,7 +104,7 @@ class PodMonitor @Inject constructor( pods.putAll(deviceCache) - newPods.forEach { + newPods.map { it.device }.forEach { deviceCache[it.identifier] = it pods[it.identifier] = it } @@ -111,6 +112,9 @@ class PodMonitor @Inject constructor( val presorted = pods.values.sortPodsToInterest() val main = presorted.determineMainDevice() + newPods?.firstOrNull { it.device.identifier == main?.identifier }?.let { + podDeviceCache.saveMainDevice(it.scanResult) + } presorted.sortedByDescending { it == main } } diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/PodFactory.kt b/app-common/src/main/java/eu/darken/capod/pods/core/PodFactory.kt index af3fc912..9f88ddf3 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/PodFactory.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/PodFactory.kt @@ -16,7 +16,7 @@ class PodFactory @Inject constructor( private val unknownFactory: UnknownDeviceFactory, ) { - suspend fun createPod(scanResult: BleScanResult): PodDevice? { + suspend fun createPod(scanResult: BleScanResult): Result? { log(TAG, VERBOSE) { "Trying to create Pod for $scanResult" } log(TAG, INFO) { "Decoding $scanResult" } @@ -29,9 +29,16 @@ class PodFactory @Inject constructor( } log(TAG, INFO) { "Pod created: $device" } - return device + return device?.let { + Result(scanResult = scanResult, device = it) + } } + data class Result( + val scanResult: BleScanResult, + val device: PodDevice + ) + companion object { private val TAG = logTag("Pod", "Factory") } diff --git a/app-wear/src/main/java/eu/darken/capod/wear/core/CAPodTileService.kt b/app-wear/src/main/java/eu/darken/capod/wear/core/CAPodTileService.kt index 95460256..873d26f0 100644 --- a/app-wear/src/main/java/eu/darken/capod/wear/core/CAPodTileService.kt +++ b/app-wear/src/main/java/eu/darken/capod/wear/core/CAPodTileService.kt @@ -4,38 +4,31 @@ import androidx.wear.tiles.* import com.google.android.horologist.tiles.CoroutinesTileService import eu.darken.capod.R -private const val RESOURCES_VERSION = "0" - class CAPodTileService : CoroutinesTileService() { override suspend fun resourcesRequest( requestParams: RequestBuilders.ResourcesRequest - ): ResourceBuilders.Resources { - return ResourceBuilders.Resources.Builder() - .setVersion(RESOURCES_VERSION) - .build() - } + ): ResourceBuilders.Resources = ResourceBuilders.Resources.Builder().apply { + setVersion(RESOURCES_VERSION) + }.build() override suspend fun tileRequest( requestParams: RequestBuilders.TileRequest - ): TileBuilders.Tile { + ): TileBuilders.Tile = TileBuilders.Tile.Builder().apply { val singleTileTimeline = TimelineBuilders.Timeline.Builder() .addTimelineEntry( - TimelineBuilders.TimelineEntry.Builder() - .setLayout( - LayoutElementBuilders.Layout.Builder() - .setRoot(tileLayout()) - .build() + TimelineBuilders.TimelineEntry.Builder().apply { + setLayout( + LayoutElementBuilders.Layout.Builder().apply { + setRoot(tileLayout()) + }.build() ) - .build() + }.build() ) .build() - - return TileBuilders.Tile.Builder() - .setResourcesVersion(RESOURCES_VERSION) - .setTimeline(singleTileTimeline) - .build() - } + setResourcesVersion(RESOURCES_VERSION) + setTimeline(singleTileTimeline) + }.build() private fun tileLayout(): LayoutElementBuilders.LayoutElement { val text = getString(R.string.app_name) @@ -50,4 +43,8 @@ class CAPodTileService : CoroutinesTileService() { ) .build() } + + companion object { + private const val RESOURCES_VERSION = "0" + } } \ No newline at end of file