From 6c893ed6f3b544b79052482565a6fb915591981b Mon Sep 17 00:00:00 2001 From: darken Date: Sun, 9 Jan 2022 17:55:55 +0100 Subject: [PATCH] Move pod reactions play/pause into extra class, check current state to only toggle when necessary. --- .../common/bluetooth/BluetoothDevice2.kt | 4 +- .../darken/capod/monitor/core/KnownDevices.kt | 32 +++++++ .../darken/capod/monitor/core/PodReactions.kt | 55 ++++++++++++ .../monitor/core/worker/MonitorWorker.kt | 88 ++++++------------- .../core/worker/MonitorWorkerEntryPoint.kt | 7 +- 5 files changed, 116 insertions(+), 70 deletions(-) create mode 100644 app/src/main/java/eu/darken/capod/monitor/core/KnownDevices.kt create mode 100644 app/src/main/java/eu/darken/capod/monitor/core/PodReactions.kt diff --git a/app/src/main/java/eu/darken/capod/common/bluetooth/BluetoothDevice2.kt b/app/src/main/java/eu/darken/capod/common/bluetooth/BluetoothDevice2.kt index 9f06c576..2c92331c 100644 --- a/app/src/main/java/eu/darken/capod/common/bluetooth/BluetoothDevice2.kt +++ b/app/src/main/java/eu/darken/capod/common/bluetooth/BluetoothDevice2.kt @@ -7,8 +7,8 @@ import kotlinx.parcelize.Parcelize @Parcelize data class BluetoothDevice2( - private val bluetoothDevice: BluetoothDevice + private val device: BluetoothDevice ) : Parcelable { - fun hasFeature(uuid: ParcelUuid): Boolean = bluetoothDevice.hasFeature(uuid) + fun hasFeature(uuid: ParcelUuid): Boolean = device.hasFeature(uuid) } \ No newline at end of file diff --git a/app/src/main/java/eu/darken/capod/monitor/core/KnownDevices.kt b/app/src/main/java/eu/darken/capod/monitor/core/KnownDevices.kt new file mode 100644 index 00000000..63dc6500 --- /dev/null +++ b/app/src/main/java/eu/darken/capod/monitor/core/KnownDevices.kt @@ -0,0 +1,32 @@ +package eu.darken.capod.monitor.core + +import eu.darken.capod.common.bluetooth.BluetoothDevice2 +import eu.darken.capod.common.bluetooth.BluetoothManager2 +import eu.darken.capod.common.coroutine.AppScope +import eu.darken.capod.common.flow.replayingShare +import eu.darken.capod.pods.core.apple.protocol.ContinuityProtocol +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class KnownDevices @Inject constructor( + @AppScope private val scope: CoroutineScope, + private val bluetoothManager2: BluetoothManager2, +) { + + fun currentKnownDevices(): Flow> = bluetoothManager2 + .isBluetoothEnabled + .flatMapLatest { bluetoothManager2.connectedDevices() } + .map { devices -> + devices.filter { device -> + ContinuityProtocol.BLE_FEATURE_UUIDS.any { feature -> + device.hasFeature(feature) + } + } + } + .replayingShare(scope) +} \ No newline at end of file diff --git a/app/src/main/java/eu/darken/capod/monitor/core/PodReactions.kt b/app/src/main/java/eu/darken/capod/monitor/core/PodReactions.kt new file mode 100644 index 00000000..2f92d8cc --- /dev/null +++ b/app/src/main/java/eu/darken/capod/monitor/core/PodReactions.kt @@ -0,0 +1,55 @@ +package eu.darken.capod.monitor.core + +import dagger.Reusable +import eu.darken.capod.common.MediaControl +import eu.darken.capod.common.debug.logging.log +import eu.darken.capod.common.debug.logging.logTag +import eu.darken.capod.common.flow.setupCommonEventHandlers +import eu.darken.capod.common.flow.withPrevious +import eu.darken.capod.main.core.GeneralSettings +import eu.darken.capod.pods.core.HasEarDetection +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.onEach +import javax.inject.Inject + +@Reusable +class PodReactions @Inject constructor( + private val podMonitor: PodMonitor, + private val knownDevices: KnownDevices, + private val generalSettings: GeneralSettings, + private val mediaControl: MediaControl, +) { + + fun podReactions() = knownDevices.currentKnownDevices() + .flatMapLatest { + if (it.isEmpty()) { + log(TAG) { "No known devices connected." } + emptyFlow() + } else { + log(TAG) { "Known devices connected: $it" } + podMonitor.mainDevice + } + } + .setupCommonEventHandlers(TAG) { "podReactions" } + .distinctUntilChanged() + .withPrevious() + .onEach { (previous, current) -> + if (previous is HasEarDetection && current is HasEarDetection) { + log(TAG) { "previous=${previous.isBeingWorn}, current=${current.isBeingWorn}" } + log(TAG) { "previous-id=${previous.identifier}, current-id=${current.identifier}" } + if (previous.identifier == current.identifier && previous.isBeingWorn != current.isBeingWorn) { + if (current.isBeingWorn && generalSettings.autoPlay.value && !mediaControl.isPlaying) { + mediaControl.sendPlay() + } else if (!current.isBeingWorn && generalSettings.autoPause.value && mediaControl.isPlaying) { + mediaControl.sendPause() + } + } + } + } + + companion object { + private val TAG = logTag("Monitor", "Reactions") + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorWorker.kt b/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorWorker.kt index 92be633c..cfd62be2 100644 --- a/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorWorker.kt +++ b/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorWorker.kt @@ -8,25 +8,19 @@ import androidx.work.WorkerParameters import dagger.assisted.Assisted import dagger.assisted.AssistedInject import dagger.hilt.EntryPoints -import eu.darken.capod.common.MediaControl import eu.darken.capod.common.debug.Bugs import eu.darken.capod.common.debug.logging.Logging.Priority.* 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 eu.darken.capod.common.flow.setupCommonEventHandlers -import eu.darken.capod.common.flow.withPrevious import eu.darken.capod.main.core.GeneralSettings import eu.darken.capod.main.core.MonitorMode import eu.darken.capod.main.core.PermissionTool -import eu.darken.capod.monitor.core.MonitorComponent -import eu.darken.capod.monitor.core.MonitorCoroutineScope +import eu.darken.capod.monitor.core.* import eu.darken.capod.monitor.ui.MonitorNotifications -import eu.darken.capod.pods.core.HasEarDetection -import eu.darken.capod.pods.core.apple.protocol.ContinuityProtocol import kotlinx.coroutines.cancel import kotlinx.coroutines.cancelChildren -import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* @@ -39,8 +33,10 @@ class MonitorWorker @AssistedInject constructor( private val monitorNotifications: MonitorNotifications, private val notificationManager: NotificationManager, private val generalSettings: GeneralSettings, - private val mediaControl: MediaControl, private val permissionTool: PermissionTool, + private val knownDevices: KnownDevices, + private val podMonitor: PodMonitor, + private val podReactions: PodReactions, ) : CoroutineWorker(context, params) { private val workerScope = MonitorCoroutineScope() @@ -52,15 +48,7 @@ class MonitorWorker @AssistedInject constructor( EntryPoints.get(monitorComponent, MonitorWorkerEntryPoint::class.java) } - private val podMonitor by lazy { - entryPoint.podMonitor() - } - private val bluetoothManager2 by lazy { - entryPoint.bluetoothManager2() - } private var finishedWithError = false - private var monitorFlowError: Throwable? = null - private var podFlowError: Throwable? = null init { log(TAG, VERBOSE) { "init(): workerId=$id" } @@ -70,11 +58,7 @@ class MonitorWorker @AssistedInject constructor( val start = System.currentTimeMillis() log(TAG, VERBOSE) { "Executing $inputData now (runAttemptCount=$runAttemptCount)" } - coroutineScope { doDoWork() - } - monitorFlowError?.let { throw it } - podFlowError?.let { throw it } val duration = System.currentTimeMillis() - start @@ -97,6 +81,21 @@ class MonitorWorker @AssistedInject constructor( return } + val monitorJob = podMonitor.mainDevice + .setupCommonEventHandlers(TAG) { "PodMonitor" } + .onStart { setForeground(monitorNotifications.getForegroundInfo(null)) } + .distinctUntilChanged() + .onEach { currentDevice -> + notificationManager.notify( + MonitorNotifications.NOTIFICATION_ID, + monitorNotifications.getNotification(currentDevice) + ) + } + .catch { + log(TAG, WARN) { "Pod Flow failed:\n${it.asLog()}" } + } + .launchIn(workerScope) + generalSettings.monitorMode.flow .flatMapLatest { monitorMode -> val missingPermsFlow = permissionTool.missingPermissions() @@ -106,17 +105,9 @@ class MonitorWorker @AssistedInject constructor( return@flatMapLatest emptyFlow() } - bluetoothManager2 - .isBluetoothEnabled - .flatMapLatest { bluetoothManager2.connectedDevices() } - .map { devices -> - devices.filter { device -> - ContinuityProtocol.BLE_FEATURE_UUIDS.any { feature -> - device.hasFeature(feature) - } - } - } - .map { knownDevices -> monitorMode to knownDevices } + knownDevices.currentKnownDevices().map { knownDevices -> + monitorMode to knownDevices + } } .setupCommonEventHandlers(TAG) { "MonitorMode" } .flatMapLatest { (monitorMode, devices) -> @@ -127,12 +118,12 @@ class MonitorWorker @AssistedInject constructor( workerScope.coroutineContext.cancelChildren() } MonitorMode.ALWAYS -> emptyFlow() - MonitorMode.AUTOMATIC -> flow { + MonitorMode.AUTOMATIC -> flow { if (devices.isNotEmpty()) { log(TAG) { "Pods are connected, aborting any timeout." } } else { log(TAG) { "No Pods are connected, canceling worker soon." } - delay(60 * 1000) + delay(30 * 1000) log(TAG) { "Canceling worker now, still no Pods connected." } workerScope.coroutineContext.cancelChildren() @@ -141,40 +132,13 @@ class MonitorWorker @AssistedInject constructor( } } .catch { - monitorFlowError = it log(TAG, WARN) { "MonitorMode Flow failed:\n${it.asLog()}" } } .launchIn(workerScope) - val monitorJob = podMonitor.mainDevice - .setupCommonEventHandlers(TAG) { "PodMonitor" } - .onStart { - setForeground(monitorNotifications.getForegroundInfo(null)) - } - .distinctUntilChanged() - .onEach { currentDevice -> - notificationManager.notify( - MonitorNotifications.NOTIFICATION_ID, - monitorNotifications.getNotification(currentDevice) - ) - } - .withPrevious() - .onEach { (previous, current) -> - if (previous is HasEarDetection && current is HasEarDetection) { - log(TAG) { "previous=${previous.isBeingWorn}, current=${current.isBeingWorn}" } - log(TAG) { "previous-id=${previous.identifier}, current-id=${current.identifier}" } - if (previous.identifier == current.identifier && previous.isBeingWorn != current.isBeingWorn) { - if (generalSettings.autoPlay.value && !mediaControl.isPlaying) { - mediaControl.sendPlay() - } else if (generalSettings.autoPause.value && mediaControl.isPlaying) { - mediaControl.sendPause() - } - } - } - } + podReactions.podReactions() .catch { - podFlowError = it - log(TAG, WARN) { "Pod Flow failed:\n${it.asLog()}" } + log(TAG, WARN) { "Pod reactions failed:\n${it.asLog()}" } } .launchIn(workerScope) diff --git a/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorWorkerEntryPoint.kt b/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorWorkerEntryPoint.kt index 3cf13d07..3b9c3d36 100644 --- a/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorWorkerEntryPoint.kt +++ b/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorWorkerEntryPoint.kt @@ -2,13 +2,8 @@ package eu.darken.capod.monitor.core.worker import dagger.hilt.EntryPoint import dagger.hilt.InstallIn -import eu.darken.capod.common.bluetooth.BluetoothManager2 import eu.darken.capod.monitor.core.MonitorComponent -import eu.darken.capod.monitor.core.PodMonitor @InstallIn(MonitorComponent::class) @EntryPoint -interface MonitorWorkerEntryPoint { - fun podMonitor(): PodMonitor - fun bluetoothManager2(): BluetoothManager2 -} \ No newline at end of file +interface MonitorWorkerEntryPoint \ No newline at end of file