mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
Autoconnection wip
This commit is contained in:
@@ -7,8 +7,11 @@ import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class BluetoothDevice2(
|
||||
private val device: BluetoothDevice
|
||||
val device: BluetoothDevice
|
||||
) : Parcelable {
|
||||
|
||||
val name: String?
|
||||
get() = device.name
|
||||
|
||||
fun hasFeature(uuid: ParcelUuid): Boolean = device.hasFeature(uuid)
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
package eu.darken.capod.common.bluetooth
|
||||
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.bluetooth.BluetoothProfile
|
||||
import android.bluetooth.*
|
||||
import android.bluetooth.le.BluetoothLeScanner
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
@@ -13,16 +10,22 @@ import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import eu.darken.capod.common.coroutine.DispatcherProvider
|
||||
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.pods.core.apple.protocol.ContinuityProtocol
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@@ -93,7 +96,7 @@ class BluetoothManager2 @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun connectedDevices(profile: Int = BluetoothProfile.HEADSET): Flow<Set<BluetoothDevice2>> = callbackFlow {
|
||||
private fun connectedDevicesForProfile(profile: Int): Flow<Set<BluetoothDevice2>> = callbackFlow {
|
||||
log(TAG) { "connectedDevices(profile=$profile) starting" }
|
||||
trySend(getBluetoothProfile(profile).connectedDevices)
|
||||
|
||||
@@ -147,6 +150,31 @@ class BluetoothManager2 @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun connectedDevices(): Flow<List<BluetoothDevice2>> = isBluetoothEnabled
|
||||
.flatMapLatest { connectedDevicesForProfile(BluetoothProfile.HEADSET) }
|
||||
.map { devices ->
|
||||
devices.filter { device ->
|
||||
ContinuityProtocol.BLE_FEATURE_UUIDS.any { feature ->
|
||||
device.hasFeature(feature)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun bondedDevices(): List<BluetoothDevice2> = adapter.bondedDevices
|
||||
.map { BluetoothDevice2(it) }
|
||||
|
||||
suspend fun nudgeConnection(device: BluetoothDevice2): Boolean = try {
|
||||
val connectMethod = BluetoothHeadset::class.java.getDeclaredMethod(
|
||||
"connect", BluetoothDevice::class.java
|
||||
).apply { isAccessible = true }
|
||||
connectMethod.invoke(getBluetoothProfile().profile, device.device)
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
log(TAG, WARN) { "BluetoothHeadset.connect(device) is unavailable:\n${e.asLog()}" }
|
||||
Bugs.report(e)
|
||||
false
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = logTag("Bluetooth", "Manager2")
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
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 PairedDevices @Inject constructor(
|
||||
@AppScope private val scope: CoroutineScope,
|
||||
private val bluetoothManager2: BluetoothManager2,
|
||||
) {
|
||||
|
||||
fun connectedDevices(): Flow<List<BluetoothDevice2>> = bluetoothManager2
|
||||
.isBluetoothEnabled
|
||||
.flatMapLatest { bluetoothManager2.connectedDevices() }
|
||||
.map { devices ->
|
||||
devices.filter { device ->
|
||||
ContinuityProtocol.BLE_FEATURE_UUIDS.any { feature ->
|
||||
device.hasFeature(feature)
|
||||
}
|
||||
}
|
||||
}
|
||||
.replayingShare(scope)
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import androidx.work.WorkerParameters
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.hilt.EntryPoints
|
||||
import eu.darken.capod.common.bluetooth.BluetoothManager2
|
||||
import eu.darken.capod.common.debug.Bugs
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.*
|
||||
import eu.darken.capod.common.debug.logging.asLog
|
||||
@@ -19,6 +20,7 @@ import eu.darken.capod.main.core.MonitorMode
|
||||
import eu.darken.capod.main.core.PermissionTool
|
||||
import eu.darken.capod.monitor.core.*
|
||||
import eu.darken.capod.monitor.ui.MonitorNotifications
|
||||
import eu.darken.capod.reactions.core.ReactionsHub
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -34,9 +36,9 @@ class MonitorWorker @AssistedInject constructor(
|
||||
private val notificationManager: NotificationManager,
|
||||
private val generalSettings: GeneralSettings,
|
||||
private val permissionTool: PermissionTool,
|
||||
private val pairedDevices: PairedDevices,
|
||||
private val podMonitor: PodMonitor,
|
||||
private val podReactions: PodReactions,
|
||||
private val reactionsHub: ReactionsHub,
|
||||
private val bluetoothManager: BluetoothManager2,
|
||||
) : CoroutineWorker(context, params) {
|
||||
|
||||
private val workerScope = MonitorCoroutineScope()
|
||||
@@ -105,7 +107,7 @@ class MonitorWorker @AssistedInject constructor(
|
||||
return@flatMapLatest emptyFlow()
|
||||
}
|
||||
|
||||
pairedDevices.connectedDevices().map { knownDevices ->
|
||||
bluetoothManager.connectedDevices().map { knownDevices ->
|
||||
monitorMode to knownDevices
|
||||
}
|
||||
}
|
||||
@@ -136,7 +138,7 @@ class MonitorWorker @AssistedInject constructor(
|
||||
}
|
||||
.launchIn(workerScope)
|
||||
|
||||
podReactions.podReactions()
|
||||
reactionsHub.monitor()
|
||||
.catch {
|
||||
log(TAG, WARN) { "Pod reactions failed:\n${it.asLog()}" }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package eu.darken.capod.reactions.core
|
||||
|
||||
import eu.darken.capod.common.bluetooth.BluetoothManager2
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.monitor.core.PodMonitor
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AutoConnect @Inject constructor(
|
||||
private val bluetoothManager: BluetoothManager2,
|
||||
private val podMonitor: PodMonitor,
|
||||
) {
|
||||
|
||||
fun monitor(): Flow<Unit> = podMonitor.mainDevice
|
||||
.map {
|
||||
val podDevice = bluetoothManager2.bondedDevices().firstOrNull { it.name?.contains("AirPod") ?: false }
|
||||
bluetoothManager2.nudgeConnection(podDevice!!)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = logTag("Reactions", "AutoConnect")
|
||||
}
|
||||
}
|
||||
+7
-5
@@ -1,12 +1,14 @@
|
||||
package eu.darken.capod.monitor.core
|
||||
package eu.darken.capod.reactions.core
|
||||
|
||||
import dagger.Reusable
|
||||
import eu.darken.capod.common.MediaControl
|
||||
import eu.darken.capod.common.bluetooth.BluetoothManager2
|
||||
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.monitor.core.PodMonitor
|
||||
import eu.darken.capod.pods.core.HasEarDetection
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
@@ -15,14 +17,14 @@ import kotlinx.coroutines.flow.onEach
|
||||
import javax.inject.Inject
|
||||
|
||||
@Reusable
|
||||
class PodReactions @Inject constructor(
|
||||
class PlayPause @Inject constructor(
|
||||
private val podMonitor: PodMonitor,
|
||||
private val pairedDevices: PairedDevices,
|
||||
private val bluetoothManager: BluetoothManager2,
|
||||
private val generalSettings: GeneralSettings,
|
||||
private val mediaControl: MediaControl,
|
||||
) {
|
||||
|
||||
fun podReactions() = pairedDevices.connectedDevices()
|
||||
fun monitor() = bluetoothManager.connectedDevices()
|
||||
.flatMapLatest {
|
||||
if (it.isEmpty()) {
|
||||
log(TAG) { "No known devices connected." }
|
||||
@@ -50,6 +52,6 @@ class PodReactions @Inject constructor(
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = logTag("Monitor", "Reactions")
|
||||
private val TAG = logTag("Reactions", "PlayPause")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package eu.darken.capod.reactions.core
|
||||
|
||||
import dagger.Reusable
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import javax.inject.Inject
|
||||
|
||||
@Reusable
|
||||
class ReactionsHub @Inject constructor(
|
||||
private val playPause: PlayPause,
|
||||
private val autoConnect: AutoConnect,
|
||||
) {
|
||||
|
||||
fun monitor(): Flow<Unit> = combine(
|
||||
playPause.monitor(),
|
||||
autoConnect.monitor()
|
||||
) { _, _ ->
|
||||
Unit
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = logTag("Reactions", "Hub")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user