mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-15 02:36:12 -04:00
Move pod reactions play/pause into extra class, check current state to only toggle when necessary.
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user