mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-02-01 07:39:11 +00:00
android: move attmanager to service to avoid trying to connect multiple times
This commit is contained in:
@@ -42,31 +42,12 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import me.kavishdevar.librepods.R
|
||||
import me.kavishdevar.librepods.services.ServiceManager
|
||||
import me.kavishdevar.librepods.utils.ATTManager
|
||||
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||
|
||||
@Composable
|
||||
fun AudioSettings() {
|
||||
val isDarkTheme = isSystemInDarkTheme()
|
||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||
val attManager = ATTManager(ServiceManager.getService()?.device?: throw IllegalStateException("No device connected"))
|
||||
DisposableEffect(attManager) {
|
||||
onDispose {
|
||||
try {
|
||||
attManager.disconnect()
|
||||
} catch (e: Exception) {
|
||||
Log.w("AirPodsAudioSettings", "Error while disconnecting ATTManager: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
Log.d("AirPodsAudioSettings", "Connecting to ATT...")
|
||||
try {
|
||||
attManager.connect()
|
||||
} catch (e: Exception) {
|
||||
Log.w("AirPodsAudioSettings", "Error while connecting ATTManager: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.audio).uppercase(),
|
||||
@@ -103,7 +84,7 @@ fun AudioSettings() {
|
||||
.padding(start = 12.dp, end = 0.dp)
|
||||
)
|
||||
|
||||
LoudSoundReductionSwitch(attManager)
|
||||
LoudSoundReductionSwitch()
|
||||
HorizontalDivider(
|
||||
thickness = 1.5.dp,
|
||||
color = Color(0x40888888),
|
||||
|
||||
@@ -42,7 +42,6 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import me.kavishdevar.librepods.R
|
||||
import me.kavishdevar.librepods.services.ServiceManager
|
||||
import me.kavishdevar.librepods.utils.ATTManager
|
||||
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -50,26 +50,26 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import kotlinx.coroutines.delay
|
||||
import me.kavishdevar.librepods.R
|
||||
import me.kavishdevar.librepods.services.ServiceManager
|
||||
import me.kavishdevar.librepods.utils.ATTManager
|
||||
import me.kavishdevar.librepods.utils.ATTHandles
|
||||
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||
|
||||
@Composable
|
||||
fun LoudSoundReductionSwitch(attManager: ATTManager) {
|
||||
fun LoudSoundReductionSwitch() {
|
||||
var loudSoundReductionEnabled by remember {
|
||||
mutableStateOf(
|
||||
false
|
||||
)
|
||||
}
|
||||
val attManager = ServiceManager.getService()?.attManager ?: throw IllegalStateException("ATTManager not available")
|
||||
LaunchedEffect(Unit) {
|
||||
while (attManager.socket?.isConnected != true) {
|
||||
delay(100)
|
||||
}
|
||||
attManager.enableNotifications(0x1b)
|
||||
attManager.enableNotifications(ATTHandles.LOUD_SOUND_REDUCTION)
|
||||
|
||||
var parsed = false
|
||||
for (attempt in 1..3) {
|
||||
try {
|
||||
val data = attManager.read(0x1b)
|
||||
val data = attManager.read(ATTHandles.LOUD_SOUND_REDUCTION)
|
||||
if (data.size == 2) {
|
||||
loudSoundReductionEnabled = data[1].toInt() != 0
|
||||
Log.d("LoudSoundReduction", "Read attempt $attempt: enabled=${loudSoundReductionEnabled}")
|
||||
@@ -90,7 +90,7 @@ fun LoudSoundReductionSwitch(attManager: ATTManager) {
|
||||
|
||||
LaunchedEffect(loudSoundReductionEnabled) {
|
||||
if (attManager.socket?.isConnected != true) return@LaunchedEffect
|
||||
attManager.write(0x1b, if (loudSoundReductionEnabled) byteArrayOf(1) else byteArrayOf(0))
|
||||
attManager.write(ATTHandles.LOUD_SOUND_REDUCTION, if (loudSoundReductionEnabled) byteArrayOf(1) else byteArrayOf(0))
|
||||
}
|
||||
|
||||
val loudSoundListener = remember {
|
||||
@@ -107,12 +107,12 @@ fun LoudSoundReductionSwitch(attManager: ATTManager) {
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
attManager.registerListener(0x1b, loudSoundListener)
|
||||
attManager.registerListener(ATTHandles.LOUD_SOUND_REDUCTION, loudSoundListener)
|
||||
}
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
attManager.unregisterListener(0x1b, loudSoundListener)
|
||||
attManager.unregisterListener(ATTHandles.LOUD_SOUND_REDUCTION, loudSoundListener)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,7 @@ import androidx.compose.ui.unit.sp
|
||||
import dev.chrisbanes.haze.HazeEffectScope
|
||||
import dev.chrisbanes.haze.HazeState
|
||||
import dev.chrisbanes.haze.hazeEffect
|
||||
import dev.chrisbanes.haze.hazeSource
|
||||
import dev.chrisbanes.haze.materials.CupertinoMaterials
|
||||
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -102,6 +103,7 @@ import me.kavishdevar.librepods.composables.ToneVolumeSlider
|
||||
import me.kavishdevar.librepods.composables.VolumeControlSwitch
|
||||
import me.kavishdevar.librepods.services.ServiceManager
|
||||
import me.kavishdevar.librepods.utils.ATTManager
|
||||
import me.kavishdevar.librepods.utils.ATTHandles
|
||||
import me.kavishdevar.librepods.utils.AACPManager
|
||||
import me.kavishdevar.librepods.utils.RadareOffsetFinder
|
||||
import java.io.IOException
|
||||
@@ -123,7 +125,7 @@ fun AccessibilitySettingsScreen() {
|
||||
val verticalScrollState = rememberScrollState()
|
||||
val hazeState = remember { HazeState() }
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val attManager = ATTManager(ServiceManager.getService()?.device?: throw IllegalStateException("No device connected"))
|
||||
val attManager = ServiceManager.getService()?.attManager ?: throw IllegalStateException("ATTManager not available")
|
||||
// get the AACP manager if available (used for EQ read/write)
|
||||
val aacpManager = remember { ServiceManager.getService()?.aacpManager }
|
||||
val context = LocalContext.current
|
||||
@@ -135,17 +137,6 @@ fun AccessibilitySettingsScreen() {
|
||||
val thumbColor = if (isDarkTheme) Color(0xFFFFFFFF) else Color(0xFFFFFFFF)
|
||||
val labelTextColor = if (isDarkTheme) Color.White else Color.Black
|
||||
|
||||
DisposableEffect(attManager) {
|
||||
onDispose {
|
||||
Log.d(TAG, "Disconnecting from ATT...")
|
||||
try {
|
||||
attManager.disconnect()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Error while disconnecting ATTManager: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
containerColor = if (isSystemInDarkTheme()) Color(
|
||||
0xFF000000
|
||||
@@ -198,6 +189,7 @@ fun AccessibilitySettingsScreen() {
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.hazeSource(hazeState)
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
.padding(horizontal = 16.dp)
|
||||
@@ -367,20 +359,15 @@ fun AccessibilitySettingsScreen() {
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
attManager.unregisterListener(0x18, transparencyListener)
|
||||
attManager.unregisterListener(ATTHandles.TRANSPARENCY, transparencyListener)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
Log.d(TAG, "Connecting to ATT...")
|
||||
try {
|
||||
attManager.connect()
|
||||
while (attManager.socket?.isConnected != true) {
|
||||
delay(100)
|
||||
}
|
||||
|
||||
attManager.enableNotifications(0x18)
|
||||
attManager.registerListener(0x18, transparencyListener)
|
||||
attManager.enableNotifications(ATTHandles.TRANSPARENCY)
|
||||
attManager.registerListener(ATTHandles.TRANSPARENCY, transparencyListener)
|
||||
|
||||
// If we have an AACP manager, prefer its EQ data to populate EQ controls first
|
||||
try {
|
||||
@@ -407,7 +394,7 @@ fun AccessibilitySettingsScreen() {
|
||||
for (attempt in 1..3) {
|
||||
initialReadAttempts.value = attempt
|
||||
try {
|
||||
val data = attManager.read(0x18)
|
||||
val data = attManager.read(ATTHandles.TRANSPARENCY)
|
||||
parsedSettings = parseTransparencySettingsResponse(data = data)
|
||||
if (parsedSettings != null) {
|
||||
Log.d(TAG, "Parsed settings on attempt $attempt")
|
||||
@@ -569,7 +556,7 @@ fun AccessibilitySettingsScreen() {
|
||||
ToneVolumeSlider()
|
||||
SinglePodANCSwitch()
|
||||
VolumeControlSwitch()
|
||||
LoudSoundReductionSwitch(attManager)
|
||||
LoudSoundReductionSwitch()
|
||||
|
||||
DropdownMenuComponent(
|
||||
label = "Press Speed",
|
||||
@@ -1113,7 +1100,7 @@ private fun sendTransparencySettings(
|
||||
|
||||
val data = buffer.array()
|
||||
attManager.write(
|
||||
0x18,
|
||||
ATTHandles.TRANSPARENCY,
|
||||
value = data
|
||||
)
|
||||
} catch (e: IOException) {
|
||||
|
||||
@@ -88,6 +88,7 @@ import me.kavishdevar.librepods.constants.StemAction
|
||||
import me.kavishdevar.librepods.constants.isHeadTrackingData
|
||||
import me.kavishdevar.librepods.utils.AACPManager
|
||||
import me.kavishdevar.librepods.utils.AACPManager.Companion.StemPressType
|
||||
import me.kavishdevar.librepods.utils.ATTManager
|
||||
import me.kavishdevar.librepods.utils.BLEManager
|
||||
import me.kavishdevar.librepods.utils.BluetoothConnectionManager
|
||||
import me.kavishdevar.librepods.utils.CrossDevice
|
||||
@@ -148,6 +149,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
||||
var macAddress = ""
|
||||
var localMac = ""
|
||||
lateinit var aacpManager: AACPManager
|
||||
var attManager: ATTManager? = null
|
||||
var cameraActive = false
|
||||
private var disconnectedBecauseReversed = false
|
||||
data class ServiceConfig(
|
||||
@@ -634,6 +636,8 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
||||
isConnectedLocally = false
|
||||
popupShown = false
|
||||
updateNotificationContent(false)
|
||||
attManager?.disconnect()
|
||||
attManager = null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2294,6 +2298,9 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
||||
|
||||
BluetoothConnectionManager.setCurrentConnection(socket, device)
|
||||
|
||||
attManager = ATTManager(device)
|
||||
attManager!!.connect()
|
||||
|
||||
updateNotificationContent(
|
||||
true,
|
||||
config.deviceName,
|
||||
|
||||
@@ -37,6 +37,18 @@ import java.io.OutputStream
|
||||
import java.util.concurrent.LinkedBlockingQueue
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
enum class ATTHandles(val value: Int) {
|
||||
TRANSPARENCY(0x18),
|
||||
LOUD_SOUND_REDUCTION(0x1b),
|
||||
HEARING_AID(0x2a),
|
||||
}
|
||||
|
||||
enum class ATTCCCDHandles(val value: Int) {
|
||||
TRANSPARENCY(ATTHandles.TRANSPARENCY.value + 1),
|
||||
LOUD_SOUND_REDUCTION(ATTHandles.LOUD_SOUND_REDUCTION.value + 1),
|
||||
HEARING_AID(ATTHandles.HEARING_AID.value + 1),
|
||||
}
|
||||
|
||||
class ATTManager(private val device: BluetoothDevice) {
|
||||
companion object {
|
||||
private const val TAG = "ATTManager"
|
||||
@@ -103,30 +115,43 @@ class ATTManager(private val device: BluetoothDevice) {
|
||||
}
|
||||
}
|
||||
|
||||
fun registerListener(handle: Int, listener: (ByteArray) -> Unit) {
|
||||
listeners.getOrPut(handle) { mutableListOf() }.add(listener)
|
||||
fun registerListener(handle: ATTHandles, listener: (ByteArray) -> Unit) {
|
||||
listeners.getOrPut(handle.value) { mutableListOf() }.add(listener)
|
||||
}
|
||||
|
||||
fun unregisterListener(handle: Int, listener: (ByteArray) -> Unit) {
|
||||
listeners[handle]?.remove(listener)
|
||||
fun unregisterListener(handle: ATTHandles, listener: (ByteArray) -> Unit) {
|
||||
listeners[handle.value]?.remove(listener)
|
||||
}
|
||||
|
||||
fun enableNotifications(handle: Int) {
|
||||
write(handle + 1, byteArrayOf(0x01, 0x00))
|
||||
fun enableNotifications(handle: ATTHandles) {
|
||||
write(ATTCCCDHandles.valueOf(handle.name), byteArrayOf(0x01, 0x00))
|
||||
}
|
||||
|
||||
fun read(handle: Int): ByteArray {
|
||||
val lsb = (handle and 0xFF).toByte()
|
||||
val msb = ((handle shr 8) and 0xFF).toByte()
|
||||
fun read(handle: ATTHandles): ByteArray {
|
||||
val lsb = (handle.value and 0xFF).toByte()
|
||||
val msb = ((handle.value shr 8) and 0xFF).toByte()
|
||||
val pdu = byteArrayOf(OPCODE_READ_REQUEST, lsb, msb)
|
||||
writeRaw(pdu)
|
||||
// wait for response placed into responses queue by the reader coroutine
|
||||
return readResponse()
|
||||
}
|
||||
|
||||
fun write(handle: Int, value: ByteArray) {
|
||||
val lsb = (handle and 0xFF).toByte()
|
||||
val msb = ((handle shr 8) and 0xFF).toByte()
|
||||
fun write(handle: ATTHandles, value: ByteArray) {
|
||||
val lsb = (handle.value and 0xFF).toByte()
|
||||
val msb = ((handle.value shr 8) and 0xFF).toByte()
|
||||
val pdu = byteArrayOf(OPCODE_WRITE_REQUEST, lsb, msb) + value
|
||||
writeRaw(pdu)
|
||||
// usually a Write Response (0x13) will arrive; wait for it (but discard return)
|
||||
try {
|
||||
readResponse()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "No write response received: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun write(handle: ATTCCCDHandles, value: ByteArray) {
|
||||
val lsb = (handle.value and 0xFF).toByte()
|
||||
val msb = ((handle.value shr 8) and 0xFF).toByte()
|
||||
val pdu = byteArrayOf(OPCODE_WRITE_REQUEST, lsb, msb) + value
|
||||
writeRaw(pdu)
|
||||
// usually a Write Response (0x13) will arrive; wait for it (but discard return)
|
||||
|
||||
Reference in New Issue
Block a user