Skip to content

Commit 3ace0e1

Browse files
committed
android: move attmanager to service to avoid trying to connect multiple times
1 parent ecfdc05 commit 3ace0e1

File tree

6 files changed

+64
-65
lines changed

6 files changed

+64
-65
lines changed

android/app/src/main/java/me/kavishdevar/librepods/composables/AudioSettings.kt

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -42,31 +42,12 @@ import androidx.compose.ui.unit.dp
4242
import androidx.compose.ui.unit.sp
4343
import me.kavishdevar.librepods.R
4444
import me.kavishdevar.librepods.services.ServiceManager
45-
import me.kavishdevar.librepods.utils.ATTManager
4645
import kotlin.io.encoding.ExperimentalEncodingApi
4746

4847
@Composable
4948
fun AudioSettings() {
5049
val isDarkTheme = isSystemInDarkTheme()
5150
val textColor = if (isDarkTheme) Color.White else Color.Black
52-
val attManager = ATTManager(ServiceManager.getService()?.device?: throw IllegalStateException("No device connected"))
53-
DisposableEffect(attManager) {
54-
onDispose {
55-
try {
56-
attManager.disconnect()
57-
} catch (e: Exception) {
58-
Log.w("AirPodsAudioSettings", "Error while disconnecting ATTManager: ${e.message}")
59-
}
60-
}
61-
}
62-
LaunchedEffect(Unit) {
63-
Log.d("AirPodsAudioSettings", "Connecting to ATT...")
64-
try {
65-
attManager.connect()
66-
} catch (e: Exception) {
67-
Log.w("AirPodsAudioSettings", "Error while connecting ATTManager: ${e.message}")
68-
}
69-
}
7051

7152
Text(
7253
text = stringResource(R.string.audio).uppercase(),
@@ -103,7 +84,7 @@ fun AudioSettings() {
10384
.padding(start = 12.dp, end = 0.dp)
10485
)
10586

106-
LoudSoundReductionSwitch(attManager)
87+
LoudSoundReductionSwitch()
10788
HorizontalDivider(
10889
thickness = 1.5.dp,
10990
color = Color(0x40888888),

android/app/src/main/java/me/kavishdevar/librepods/composables/ConnectionSettings.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ import androidx.compose.ui.unit.dp
4242
import androidx.compose.ui.unit.sp
4343
import me.kavishdevar.librepods.R
4444
import me.kavishdevar.librepods.services.ServiceManager
45-
import me.kavishdevar.librepods.utils.ATTManager
4645
import kotlin.io.encoding.ExperimentalEncodingApi
4746

4847
@Composable

android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,26 +50,26 @@ import androidx.compose.ui.unit.dp
5050
import androidx.compose.ui.unit.sp
5151
import kotlinx.coroutines.delay
5252
import me.kavishdevar.librepods.R
53+
import me.kavishdevar.librepods.services.ServiceManager
5354
import me.kavishdevar.librepods.utils.ATTManager
55+
import me.kavishdevar.librepods.utils.ATTHandles
5456
import kotlin.io.encoding.ExperimentalEncodingApi
5557

5658
@Composable
57-
fun LoudSoundReductionSwitch(attManager: ATTManager) {
59+
fun LoudSoundReductionSwitch() {
5860
var loudSoundReductionEnabled by remember {
5961
mutableStateOf(
6062
false
6163
)
6264
}
65+
val attManager = ServiceManager.getService()?.attManager ?: throw IllegalStateException("ATTManager not available")
6366
LaunchedEffect(Unit) {
64-
while (attManager.socket?.isConnected != true) {
65-
delay(100)
66-
}
67-
attManager.enableNotifications(0x1b)
67+
attManager.enableNotifications(ATTHandles.LOUD_SOUND_REDUCTION)
6868

6969
var parsed = false
7070
for (attempt in 1..3) {
7171
try {
72-
val data = attManager.read(0x1b)
72+
val data = attManager.read(ATTHandles.LOUD_SOUND_REDUCTION)
7373
if (data.size == 2) {
7474
loudSoundReductionEnabled = data[1].toInt() != 0
7575
Log.d("LoudSoundReduction", "Read attempt $attempt: enabled=${loudSoundReductionEnabled}")
@@ -90,7 +90,7 @@ fun LoudSoundReductionSwitch(attManager: ATTManager) {
9090

9191
LaunchedEffect(loudSoundReductionEnabled) {
9292
if (attManager.socket?.isConnected != true) return@LaunchedEffect
93-
attManager.write(0x1b, if (loudSoundReductionEnabled) byteArrayOf(1) else byteArrayOf(0))
93+
attManager.write(ATTHandles.LOUD_SOUND_REDUCTION, if (loudSoundReductionEnabled) byteArrayOf(1) else byteArrayOf(0))
9494
}
9595

9696
val loudSoundListener = remember {
@@ -107,12 +107,12 @@ fun LoudSoundReductionSwitch(attManager: ATTManager) {
107107
}
108108

109109
LaunchedEffect(Unit) {
110-
attManager.registerListener(0x1b, loudSoundListener)
110+
attManager.registerListener(ATTHandles.LOUD_SOUND_REDUCTION, loudSoundListener)
111111
}
112112

113113
DisposableEffect(Unit) {
114114
onDispose {
115-
attManager.unregisterListener(0x1b, loudSoundListener)
115+
attManager.unregisterListener(ATTHandles.LOUD_SOUND_REDUCTION, loudSoundListener)
116116
}
117117
}
118118

android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ import androidx.compose.ui.unit.sp
8686
import dev.chrisbanes.haze.HazeEffectScope
8787
import dev.chrisbanes.haze.HazeState
8888
import dev.chrisbanes.haze.hazeEffect
89+
import dev.chrisbanes.haze.hazeSource
8990
import dev.chrisbanes.haze.materials.CupertinoMaterials
9091
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
9192
import kotlinx.coroutines.CoroutineScope
@@ -102,6 +103,7 @@ import me.kavishdevar.librepods.composables.ToneVolumeSlider
102103
import me.kavishdevar.librepods.composables.VolumeControlSwitch
103104
import me.kavishdevar.librepods.services.ServiceManager
104105
import me.kavishdevar.librepods.utils.ATTManager
106+
import me.kavishdevar.librepods.utils.ATTHandles
105107
import me.kavishdevar.librepods.utils.AACPManager
106108
import me.kavishdevar.librepods.utils.RadareOffsetFinder
107109
import java.io.IOException
@@ -123,7 +125,7 @@ fun AccessibilitySettingsScreen() {
123125
val verticalScrollState = rememberScrollState()
124126
val hazeState = remember { HazeState() }
125127
val snackbarHostState = remember { SnackbarHostState() }
126-
val attManager = ATTManager(ServiceManager.getService()?.device?: throw IllegalStateException("No device connected"))
128+
val attManager = ServiceManager.getService()?.attManager ?: throw IllegalStateException("ATTManager not available")
127129
// get the AACP manager if available (used for EQ read/write)
128130
val aacpManager = remember { ServiceManager.getService()?.aacpManager }
129131
val context = LocalContext.current
@@ -135,17 +137,6 @@ fun AccessibilitySettingsScreen() {
135137
val thumbColor = if (isDarkTheme) Color(0xFFFFFFFF) else Color(0xFFFFFFFF)
136138
val labelTextColor = if (isDarkTheme) Color.White else Color.Black
137139

138-
DisposableEffect(attManager) {
139-
onDispose {
140-
Log.d(TAG, "Disconnecting from ATT...")
141-
try {
142-
attManager.disconnect()
143-
} catch (e: Exception) {
144-
Log.w(TAG, "Error while disconnecting ATTManager: ${e.message}")
145-
}
146-
}
147-
}
148-
149140
Scaffold(
150141
containerColor = if (isSystemInDarkTheme()) Color(
151142
0xFF000000
@@ -198,6 +189,7 @@ fun AccessibilitySettingsScreen() {
198189
) { paddingValues ->
199190
Column(
200191
modifier = Modifier
192+
.hazeSource(hazeState)
201193
.fillMaxSize()
202194
.padding(paddingValues)
203195
.padding(horizontal = 16.dp)
@@ -367,20 +359,15 @@ fun AccessibilitySettingsScreen() {
367359

368360
DisposableEffect(Unit) {
369361
onDispose {
370-
attManager.unregisterListener(0x18, transparencyListener)
362+
attManager.unregisterListener(ATTHandles.TRANSPARENCY, transparencyListener)
371363
}
372364
}
373365

374366
LaunchedEffect(Unit) {
375367
Log.d(TAG, "Connecting to ATT...")
376368
try {
377-
attManager.connect()
378-
while (attManager.socket?.isConnected != true) {
379-
delay(100)
380-
}
381-
382-
attManager.enableNotifications(0x18)
383-
attManager.registerListener(0x18, transparencyListener)
369+
attManager.enableNotifications(ATTHandles.TRANSPARENCY)
370+
attManager.registerListener(ATTHandles.TRANSPARENCY, transparencyListener)
384371

385372
// If we have an AACP manager, prefer its EQ data to populate EQ controls first
386373
try {
@@ -407,7 +394,7 @@ fun AccessibilitySettingsScreen() {
407394
for (attempt in 1..3) {
408395
initialReadAttempts.value = attempt
409396
try {
410-
val data = attManager.read(0x18)
397+
val data = attManager.read(ATTHandles.TRANSPARENCY)
411398
parsedSettings = parseTransparencySettingsResponse(data = data)
412399
if (parsedSettings != null) {
413400
Log.d(TAG, "Parsed settings on attempt $attempt")
@@ -569,7 +556,7 @@ fun AccessibilitySettingsScreen() {
569556
ToneVolumeSlider()
570557
SinglePodANCSwitch()
571558
VolumeControlSwitch()
572-
LoudSoundReductionSwitch(attManager)
559+
LoudSoundReductionSwitch()
573560

574561
DropdownMenuComponent(
575562
label = "Press Speed",
@@ -1113,7 +1100,7 @@ private fun sendTransparencySettings(
11131100

11141101
val data = buffer.array()
11151102
attManager.write(
1116-
0x18,
1103+
ATTHandles.TRANSPARENCY,
11171104
value = data
11181105
)
11191106
} catch (e: IOException) {

android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ import me.kavishdevar.librepods.constants.StemAction
8888
import me.kavishdevar.librepods.constants.isHeadTrackingData
8989
import me.kavishdevar.librepods.utils.AACPManager
9090
import me.kavishdevar.librepods.utils.AACPManager.Companion.StemPressType
91+
import me.kavishdevar.librepods.utils.ATTManager
9192
import me.kavishdevar.librepods.utils.BLEManager
9293
import me.kavishdevar.librepods.utils.BluetoothConnectionManager
9394
import me.kavishdevar.librepods.utils.CrossDevice
@@ -148,6 +149,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
148149
var macAddress = ""
149150
var localMac = ""
150151
lateinit var aacpManager: AACPManager
152+
var attManager: ATTManager? = null
151153
var cameraActive = false
152154
private var disconnectedBecauseReversed = false
153155
data class ServiceConfig(
@@ -634,6 +636,8 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
634636
isConnectedLocally = false
635637
popupShown = false
636638
updateNotificationContent(false)
639+
attManager?.disconnect()
640+
attManager = null
637641
}
638642
}
639643
}
@@ -2294,6 +2298,9 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
22942298

22952299
BluetoothConnectionManager.setCurrentConnection(socket, device)
22962300

2301+
attManager = ATTManager(device)
2302+
attManager!!.connect()
2303+
22972304
updateNotificationContent(
22982305
true,
22992306
config.deviceName,

android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ import java.io.OutputStream
3737
import java.util.concurrent.LinkedBlockingQueue
3838
import java.util.concurrent.TimeUnit
3939

40+
enum class ATTHandles(val value: Int) {
41+
TRANSPARENCY(0x18),
42+
LOUD_SOUND_REDUCTION(0x1b),
43+
HEARING_AID(0x2a),
44+
}
45+
46+
enum class ATTCCCDHandles(val value: Int) {
47+
TRANSPARENCY(ATTHandles.TRANSPARENCY.value + 1),
48+
LOUD_SOUND_REDUCTION(ATTHandles.LOUD_SOUND_REDUCTION.value + 1),
49+
HEARING_AID(ATTHandles.HEARING_AID.value + 1),
50+
}
51+
4052
class ATTManager(private val device: BluetoothDevice) {
4153
companion object {
4254
private const val TAG = "ATTManager"
@@ -103,30 +115,43 @@ class ATTManager(private val device: BluetoothDevice) {
103115
}
104116
}
105117

106-
fun registerListener(handle: Int, listener: (ByteArray) -> Unit) {
107-
listeners.getOrPut(handle) { mutableListOf() }.add(listener)
118+
fun registerListener(handle: ATTHandles, listener: (ByteArray) -> Unit) {
119+
listeners.getOrPut(handle.value) { mutableListOf() }.add(listener)
108120
}
109121

110-
fun unregisterListener(handle: Int, listener: (ByteArray) -> Unit) {
111-
listeners[handle]?.remove(listener)
122+
fun unregisterListener(handle: ATTHandles, listener: (ByteArray) -> Unit) {
123+
listeners[handle.value]?.remove(listener)
112124
}
113125

114-
fun enableNotifications(handle: Int) {
115-
write(handle + 1, byteArrayOf(0x01, 0x00))
126+
fun enableNotifications(handle: ATTHandles) {
127+
write(ATTCCCDHandles.valueOf(handle.name), byteArrayOf(0x01, 0x00))
116128
}
117129

118-
fun read(handle: Int): ByteArray {
119-
val lsb = (handle and 0xFF).toByte()
120-
val msb = ((handle shr 8) and 0xFF).toByte()
130+
fun read(handle: ATTHandles): ByteArray {
131+
val lsb = (handle.value and 0xFF).toByte()
132+
val msb = ((handle.value shr 8) and 0xFF).toByte()
121133
val pdu = byteArrayOf(OPCODE_READ_REQUEST, lsb, msb)
122134
writeRaw(pdu)
123135
// wait for response placed into responses queue by the reader coroutine
124136
return readResponse()
125137
}
126138

127-
fun write(handle: Int, value: ByteArray) {
128-
val lsb = (handle and 0xFF).toByte()
129-
val msb = ((handle shr 8) and 0xFF).toByte()
139+
fun write(handle: ATTHandles, value: ByteArray) {
140+
val lsb = (handle.value and 0xFF).toByte()
141+
val msb = ((handle.value shr 8) and 0xFF).toByte()
142+
val pdu = byteArrayOf(OPCODE_WRITE_REQUEST, lsb, msb) + value
143+
writeRaw(pdu)
144+
// usually a Write Response (0x13) will arrive; wait for it (but discard return)
145+
try {
146+
readResponse()
147+
} catch (e: Exception) {
148+
Log.w(TAG, "No write response received: ${e.message}")
149+
}
150+
}
151+
152+
fun write(handle: ATTCCCDHandles, value: ByteArray) {
153+
val lsb = (handle.value and 0xFF).toByte()
154+
val msb = ((handle.value shr 8) and 0xFF).toByte()
130155
val pdu = byteArrayOf(OPCODE_WRITE_REQUEST, lsb, msb) + value
131156
writeRaw(pdu)
132157
// usually a Write Response (0x13) will arrive; wait for it (but discard return)

0 commit comments

Comments
 (0)