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