Autoconnection wip

This commit is contained in:
darken
2022-01-14 01:13:11 +01:00
parent 793ea1e723
commit 12aee6e597
7 changed files with 101 additions and 47 deletions
@@ -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")
}
}
@@ -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")
}
}