Move pod reactions play/pause into extra class, check current state to only toggle when necessary.

This commit is contained in:
darken
2022-01-09 17:55:55 +01:00
parent b0e184f5d8
commit 6c893ed6f3
5 changed files with 116 additions and 70 deletions
@@ -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)
}
@@ -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<List<BluetoothDevice2>> = bluetoothManager2
.isBluetoothEnabled
.flatMapLatest { bluetoothManager2.connectedDevices() }
.map { devices ->
devices.filter { device ->
ContinuityProtocol.BLE_FEATURE_UUIDS.any { feature ->
device.hasFeature(feature)
}
}
}
.replayingShare(scope)
}
@@ -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")
}
}
@@ -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<Unit> {
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)
@@ -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
}
interface MonitorWorkerEntryPoint