android: fix rework ATT connection

This commit is contained in:
Kavish Devar
2026-05-30 12:09:05 +05:30
parent 571db0ebde
commit af4261485a
8 changed files with 143 additions and 117 deletions

View File

@@ -1143,7 +1143,7 @@ class AACPManager {
) )
} }
val socket = BluetoothConnectionManager.getCurrentSocket() ?: return false val socket = BluetoothConnectionManager.getAACPSocket() ?: return false
if (socket.isConnected) { if (socket.isConnected) {
socket.outputStream?.write(packet) socket.outputStream?.write(packet)

View File

@@ -0,0 +1,63 @@
package me.kavishdevar.librepods.bluetooth
import android.util.Log
private const val TAG = "ATTManagerv2"
// the random disconnects were because of ATT, apparently. seems like we will have to accept no notifications for external changes (mainly amplification in hearing aid)
object ATTManagerv2 {
fun readCharacteristic(handle: ATTHandles): ByteArray? {
val socket = BluetoothConnectionManager.getATTSocket()?: return null
try {
// socket.connect()
val input = socket.inputStream
val output = socket.outputStream
val pdu = byteArrayOf(0x0A, handle.value.toByte(), 0x00)
output.write(pdu)
output.flush()
Log.d(TAG, "writeRaw: ${pdu.joinToString(" ") { String.format("%02X", it) }}")
val buffer = ByteArray(512)
val len = input.read(buffer)
if (len == -1) {
throw IllegalStateException("End of stream reached")
}
val data = buffer.copyOfRange(0, len)
// socket.close()
if (data[0] != 0x0B.toByte()) {
throw IllegalStateException("Invalid response: ${data.joinToString(" ") { String.format("%02X", it) }}")
}
Log.d(TAG, "readPDU: ${data.joinToString(" ") { String.format("%02X", it) }}")
return data.copyOfRange(1, data.size)
} catch (e: Exception) {
Log.e(TAG, "Error reading characteristic: ${e.message}")
return null
}
}
fun writeCharacteristic(handle: ATTHandles, data: ByteArray) {
val socket = BluetoothConnectionManager.getATTSocket()?: return
try {
// socket.connect()
val input = socket.inputStream
val output = socket.outputStream
val pdu = byteArrayOf(0x12, handle.value.toByte(), 0x00) + data // 0x0 because LE
output.write(pdu)
output.flush()
Log.d(TAG, "writeRaw: ${pdu.joinToString(" ") { String.format("%02X", it) }}")
val buffer = ByteArray(512)
val len = input.read(buffer)
if (len == -1) {
throw IllegalStateException("End of stream reached")
}
val resp = buffer.copyOfRange(0, len)
// socket.close()
if (!resp.contentEquals(byteArrayOf(0x13))) {
throw IllegalStateException("Invalid response: ${resp.joinToString(" ") { String.format("%02X", it) }}")
}
Log.d(TAG, "readPDU: ${resp.joinToString(" ") { String.format("%02X", it) }}")
} catch (e: Exception) {
Log.e(TAG, "Error writing characteristic: ${e.message}")
}
}
}

View File

@@ -18,23 +18,22 @@
package me.kavishdevar.librepods.bluetooth package me.kavishdevar.librepods.bluetooth
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothSocket import android.bluetooth.BluetoothSocket
import android.util.Log
object BluetoothConnectionManager { object BluetoothConnectionManager {
private const val TAG = "BluetoothConnectionManager" private var aacpSocket: BluetoothSocket? = null
private var attSocket: BluetoothSocket? = null
private var currentSocket: BluetoothSocket? = null fun setCurrentConnection(aacpSocket: BluetoothSocket?, attSocket: BluetoothSocket?) {
private var currentDevice: BluetoothDevice? = null BluetoothConnectionManager.aacpSocket = aacpSocket
BluetoothConnectionManager.attSocket = attSocket
fun setCurrentConnection(socket: BluetoothSocket, device: BluetoothDevice) {
currentSocket = socket
currentDevice = device
Log.d(TAG, "Current connection set to device: ${device.address}")
} }
fun getCurrentSocket(): BluetoothSocket? { fun getAACPSocket(): BluetoothSocket? {
return currentSocket return aacpSocket
}
fun getATTSocket(): BluetoothSocket? {
return attSocket
} }
} }

View File

@@ -27,6 +27,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.kavishdevar.librepods.bluetooth.ATTHandles import me.kavishdevar.librepods.bluetooth.ATTHandles
import me.kavishdevar.librepods.bluetooth.ATTManager import me.kavishdevar.librepods.bluetooth.ATTManager
import me.kavishdevar.librepods.bluetooth.ATTManagerv2
import java.io.IOException import java.io.IOException
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.ByteOrder import java.nio.ByteOrder
@@ -138,7 +139,7 @@ fun parseHearingAidSettingsResponse(data: ByteArray): HearingAidSettings? {
} }
fun sendHearingAidSettings( fun sendHearingAidSettings(
attManager: ATTManager, // attManager: ATTManager,
hearingAidSettings: HearingAidSettings, hearingAidSettings: HearingAidSettings,
debounceJob: MutableState<Job?> debounceJob: MutableState<Job?>
) { ) {
@@ -146,7 +147,7 @@ fun sendHearingAidSettings(
debounceJob.value = CoroutineScope(Dispatchers.IO).launch { debounceJob.value = CoroutineScope(Dispatchers.IO).launch {
delay(100) delay(100)
try { try {
val currentData = attManager.read(ATTHandles.HEARING_AID) val currentData = ATTManagerv2.readCharacteristic(ATTHandles.HEARING_AID)?: return@launch
Log.d(TAG, "Current data before update: ${currentData.joinToString(" ") { String.format("%02X", it) }}") Log.d(TAG, "Current data before update: ${currentData.joinToString(" ") { String.format("%02X", it) }}")
if (currentData.size < 104) { if (currentData.size < 104) {
Log.w(TAG, "Current data size ${currentData.size} too small, cannot send settings") Log.w(TAG, "Current data size ${currentData.size} too small, cannot send settings")
@@ -184,7 +185,7 @@ fun sendHearingAidSettings(
Log.d(TAG, "Sending updated settings: ${currentData.joinToString(" ") { String.format("%02X", it) }}") Log.d(TAG, "Sending updated settings: ${currentData.joinToString(" ") { String.format("%02X", it) }}")
attManager.write(ATTHandles.HEARING_AID, currentData) ATTManagerv2.writeCharacteristic(ATTHandles.HEARING_AID, currentData)
} catch (e: IOException) { } catch (e: IOException) {
e.printStackTrace() e.printStackTrace()
} }

View File

@@ -56,12 +56,14 @@ import me.kavishdevar.librepods.presentation.components.StyledToggle
import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.services.ServiceManager
import me.kavishdevar.librepods.bluetooth.AACPManager import me.kavishdevar.librepods.bluetooth.AACPManager
import me.kavishdevar.librepods.bluetooth.ATTHandles import me.kavishdevar.librepods.bluetooth.ATTHandles
import me.kavishdevar.librepods.bluetooth.ATTManagerv2
import me.kavishdevar.librepods.data.HearingAidSettings import me.kavishdevar.librepods.data.HearingAidSettings
import me.kavishdevar.librepods.data.parseHearingAidSettingsResponse import me.kavishdevar.librepods.data.parseHearingAidSettingsResponse
import me.kavishdevar.librepods.data.sendHearingAidSettings import me.kavishdevar.librepods.data.sendHearingAidSettings
import me.kavishdevar.librepods.presentation.viewmodel.AirPodsViewModel import me.kavishdevar.librepods.presentation.viewmodel.AirPodsViewModel
import java.io.IOException import java.io.IOException
import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.time.Duration.Companion.milliseconds
private var debounceJob: MutableState<Job?> = mutableStateOf(null) private var debounceJob: MutableState<Job?> = mutableStateOf(null)
private const val TAG = "HearingAidAdjustments" private const val TAG = "HearingAidAdjustments"
@@ -74,7 +76,7 @@ fun HearingAidAdjustmentsScreen(viewModel: AirPodsViewModel) {
isSystemInDarkTheme() isSystemInDarkTheme()
val verticalScrollState = rememberScrollState() val verticalScrollState = rememberScrollState()
val hazeState = remember { HazeState() } val hazeState = remember { HazeState() }
val attManager = ServiceManager.getService()?.attManager ?: throw IllegalStateException("ATTManager not available") // val attManager = ServiceManager.getService()?.attManager ?: throw IllegalStateException("ATTManager not available")
val state by viewModel.uiState.collectAsState() val state by viewModel.uiState.collectAsState()
@@ -175,20 +177,20 @@ fun HearingAidAdjustmentsScreen(viewModel: AirPodsViewModel) {
ownVoiceAmplification = ownVoiceAmplification.floatValue ownVoiceAmplification = ownVoiceAmplification.floatValue
) )
Log.d(TAG, "Updated settings: ${hearingAidSettings.value}") Log.d(TAG, "Updated settings: ${hearingAidSettings.value}")
sendHearingAidSettings(attManager, hearingAidSettings.value, debounceJob) sendHearingAidSettings(hearingAidSettings.value, debounceJob)
} }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
Log.d(TAG, "Connecting to ATT...") Log.d(TAG, "Connecting to ATT...")
try { try {
attManager.enableNotifications(ATTHandles.HEARING_AID) // attManager.enableNotifications(ATTHandles.HEARING_AID)
attManager.registerListener(ATTHandles.HEARING_AID, hearingAidATTListener) // attManager.registerListener(ATTHandles.HEARING_AID, hearingAidATTListener)
var parsedSettings: HearingAidSettings? = null var parsedSettings: HearingAidSettings? = null
for (attempt in 1..3) { for (attempt in 1..3) {
initialReadAttempts.intValue = attempt initialReadAttempts.intValue = attempt
try { try {
val data = attManager.read(ATTHandles.HEARING_AID) val data = ATTManagerv2.readCharacteristic(ATTHandles.HEARING_AID) ?: return@LaunchedEffect
parsedSettings = parseHearingAidSettingsResponse(data = data) parsedSettings = parseHearingAidSettingsResponse(data = data)
if (parsedSettings != null) { if (parsedSettings != null) {
Log.d(TAG, "Parsed settings on attempt $attempt") Log.d(TAG, "Parsed settings on attempt $attempt")
@@ -199,7 +201,7 @@ fun HearingAidAdjustmentsScreen(viewModel: AirPodsViewModel) {
} catch (e: Exception) { } catch (e: Exception) {
Log.w(TAG, "Read attempt $attempt failed: ${e.message}") Log.w(TAG, "Read attempt $attempt failed: ${e.message}")
} }
delay(200) delay(200.milliseconds)
} }
if (parsedSettings != null) { if (parsedSettings != null) {

View File

@@ -62,6 +62,7 @@ import me.kavishdevar.librepods.R
import me.kavishdevar.librepods.presentation.components.StyledScaffold import me.kavishdevar.librepods.presentation.components.StyledScaffold
import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.services.ServiceManager
import me.kavishdevar.librepods.bluetooth.ATTHandles import me.kavishdevar.librepods.bluetooth.ATTHandles
import me.kavishdevar.librepods.bluetooth.ATTManagerv2
import me.kavishdevar.librepods.data.HearingAidSettings import me.kavishdevar.librepods.data.HearingAidSettings
import me.kavishdevar.librepods.data.parseHearingAidSettingsResponse import me.kavishdevar.librepods.data.parseHearingAidSettingsResponse
import me.kavishdevar.librepods.data.sendHearingAidSettings import me.kavishdevar.librepods.data.sendHearingAidSettings
@@ -73,17 +74,17 @@ private const val TAG = "HearingAidAdjustments"
@Composable @Composable
fun UpdateHearingTestScreen() { fun UpdateHearingTestScreen() {
val verticalScrollState = rememberScrollState() val verticalScrollState = rememberScrollState()
val attManager = ServiceManager.getService()?.attManager // val attManager = ServiceManager.getService()?.attManager
if (attManager == null) { // if (attManager == null) {
Text( // Text(
text = stringResource(R.string.att_manager_is_null_try_reconnecting), // text = stringResource(R.string.att_manager_is_null_try_reconnecting),
modifier = Modifier // modifier = Modifier
.fillMaxSize() // .fillMaxSize()
.padding(16.dp), // .padding(16.dp),
textAlign = TextAlign.Center // textAlign = TextAlign.Center
) // )
return // return
} // }
val backdrop = rememberLayerBackdrop() val backdrop = rememberLayerBackdrop()
StyledScaffold( StyledScaffold(
@@ -167,11 +168,11 @@ fun UpdateHearingTestScreen() {
} }
DisposableEffect(Unit) { // DisposableEffect(Unit) {
onDispose { // onDispose {
attManager.unregisterListener(ATTHandles.HEARING_AID, hearingAidATTListener) // attManager.unregisterListener(ATTHandles.HEARING_AID, hearingAidATTListener)
} // }
} // }
LaunchedEffect( LaunchedEffect(
leftEQ.value, leftEQ.value,
@@ -214,20 +215,20 @@ fun UpdateHearingTestScreen() {
ownVoiceAmplification = ownVoiceAmplification.floatValue ownVoiceAmplification = ownVoiceAmplification.floatValue
) )
Log.d(TAG, "Updated settings: ${hearingAidSettings.value}") Log.d(TAG, "Updated settings: ${hearingAidSettings.value}")
sendHearingAidSettings(attManager, hearingAidSettings.value, debounceJob) sendHearingAidSettings(hearingAidSettings.value, debounceJob)
} }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
Log.d(TAG, "Connecting to ATT...") Log.d(TAG, "Connecting to ATT...")
try { try {
attManager.enableNotifications(ATTHandles.HEARING_AID) // attManager.enableNotifications(ATTHandles.HEARING_AID)
attManager.registerListener(ATTHandles.HEARING_AID, hearingAidATTListener) // attManager.registerListener(ATTHandles.HEARING_AID, hearingAidATTListener)
var parsedSettings: HearingAidSettings? = null var parsedSettings: HearingAidSettings? = null
for (attempt in 1..3) { for (attempt in 1..3) {
initialReadAttempts.intValue = attempt initialReadAttempts.intValue = attempt
try { try {
val data = attManager.read(ATTHandles.HEARING_AID) val data = ATTManagerv2.readCharacteristic(ATTHandles.HEARING_AID)?: byteArrayOf()
parsedSettings = parseHearingAidSettingsResponse(data = data) parsedSettings = parseHearingAidSettingsResponse(data = data)
if (parsedSettings != null) { if (parsedSettings != null) {
Log.d(TAG, "Parsed settings on attempt $attempt") Log.d(TAG, "Parsed settings on attempt $attempt")

View File

@@ -40,6 +40,7 @@ import me.kavishdevar.librepods.billing.BillingManager
import me.kavishdevar.librepods.bluetooth.AACPManager import me.kavishdevar.librepods.bluetooth.AACPManager
import me.kavishdevar.librepods.bluetooth.AACPManager.Companion.ControlCommandIdentifiers import me.kavishdevar.librepods.bluetooth.AACPManager.Companion.ControlCommandIdentifiers
import me.kavishdevar.librepods.bluetooth.ATTHandles import me.kavishdevar.librepods.bluetooth.ATTHandles
import me.kavishdevar.librepods.bluetooth.ATTManagerv2
import me.kavishdevar.librepods.data.AirPodsInstance import me.kavishdevar.librepods.data.AirPodsInstance
import me.kavishdevar.librepods.data.AirPodsModels import me.kavishdevar.librepods.data.AirPodsModels
import me.kavishdevar.librepods.data.AirPodsNotifications import me.kavishdevar.librepods.data.AirPodsNotifications
@@ -51,6 +52,7 @@ import me.kavishdevar.librepods.data.ControlCommandRepository
import me.kavishdevar.librepods.data.StemAction import me.kavishdevar.librepods.data.StemAction
import me.kavishdevar.librepods.data.XposedRemotePrefProvider import me.kavishdevar.librepods.data.XposedRemotePrefProvider
import me.kavishdevar.librepods.services.AirPodsService import me.kavishdevar.librepods.services.AirPodsService
import kotlin.time.Duration.Companion.milliseconds
@Suppress("ArrayInDataClass") @Suppress("ArrayInDataClass")
data class AirPodsUiState( data class AirPodsUiState(
@@ -530,13 +532,7 @@ class AirPodsViewModel(
} }
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
if (service.attManager?.socket?.isConnected != true) { ATTManagerv2.writeCharacteristic(handle, value)
service.attManager?.connect()
}
while (service.attManager?.socket?.isConnected != true) {
delay(250)
}
service.attManager?.write(handle, value)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
@@ -545,19 +541,14 @@ class AirPodsViewModel(
fun refreshATT() { fun refreshATT() {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
val loudSoundReduction = val loudSoundReduction = ATTManagerv2.readCharacteristic(ATTHandles.LOUD_SOUND_REDUCTION) ?: byteArrayOf()
runCatching { service.attManager?.read(ATTHandles.LOUD_SOUND_REDUCTION) }.getOrNull() val loudSoundReductionEnabled = if (loudSoundReduction.isNotEmpty()) {
val loudSoundReductionEnabled = loudSoundReduction?.size?.let {
if (it > 0) {
loudSoundReduction[0].toInt() == 1 loudSoundReduction[0].toInt() == 1
} else false } else false
} val transparencyData = ATTManagerv2.readCharacteristic(ATTHandles.TRANSPARENCY)?: byteArrayOf()
val transparencyData = val hearingAidData = ATTManagerv2.readCharacteristic(ATTHandles.HEARING_AID)?:byteArrayOf()
runCatching { service.attManager?.read(ATTHandles.TRANSPARENCY) }.getOrNull()?: byteArrayOf()
val hearingAidData =
runCatching { service.attManager?.read(ATTHandles.HEARING_AID) }.getOrNull()?: byteArrayOf()
_uiState.value = _uiState.value.copy( _uiState.value = _uiState.value.copy(
loudSoundReductionEnabled = loudSoundReductionEnabled == true, loudSoundReductionEnabled = loudSoundReductionEnabled,
transparencyData = transparencyData, transparencyData = transparencyData,
hearingAidData = hearingAidData hearingAidData = hearingAidData
) )
@@ -566,19 +557,9 @@ class AirPodsViewModel(
fun observeATT() { fun observeATT() {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
if (service.attManager?.socket?.isConnected != true) {
service.attManager?.connect()
}
while (service.attManager?.socket?.isConnected != true) {
delay(1000)
}
service.attManager?.enableNotifications(ATTHandles.LOUD_SOUND_REDUCTION)
service.attManager?.enableNotifications(ATTHandles.TRANSPARENCY)
service.attManager?.enableNotifications(ATTHandles.HEARING_AID)
while (true) { while (true) {
refreshATT() refreshATT()
delay(15000) delay(15000.milliseconds)
} }
} }
} }

View File

@@ -129,6 +129,7 @@ import java.nio.ByteOrder
import java.time.LocalDateTime import java.time.LocalDateTime
import kotlin.io.encoding.Base64 import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.time.Duration.Companion.milliseconds
private const val TAG = "AirPodsService" private const val TAG = "AirPodsService"
@@ -151,7 +152,6 @@ 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 airpodsInstance: AirPodsInstance? = null var airpodsInstance: AirPodsInstance? = null
var cameraActive = false var cameraActive = false
private var disconnectedBecauseReversed = false private var disconnectedBecauseReversed = false
@@ -654,6 +654,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
addAction("android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT") addAction("android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT")
addAction("android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED") addAction("android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED")
addAction("android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED") addAction("android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED")
addAction("android.bluetooth.device.action.UUID")
} }
connectionReceiver = object : BroadcastReceiver() { connectionReceiver = object : BroadcastReceiver() {
@@ -691,8 +692,9 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
// isConnectedLocally = false // isConnectedLocally = false
popupShown = false popupShown = false
updateNotificationContent(false) updateNotificationContent(false)
attManager?.disconnect() aacpManager.disconnected()
attManager = null BluetoothConnectionManager.getATTSocket()?.close()
BluetoothConnectionManager.setCurrentConnection(null, null)
} }
} }
} }
@@ -2432,32 +2434,6 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
takeOver("music", manualTakeOverAfterReversed = true) takeOver("music", manualTakeOverAfterReversed = true)
} }
val bluetoothManager = getSystemService(BluetoothManager::class.java)
val bluetoothAdapter = bluetoothManager.adapter
bluetoothAdapter?.bondedDevices?.forEach { device ->
device.fetchUuidsWithSdp()
if (device.uuids != null) {
// Check for the AirPods service UUID
val uuid = ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a")
if (device.uuids.contains(uuid)) {
Log.d(TAG, "Found AirPods device: ${device.name} (${device.address})")
// Connect or do whatever you need
CoroutineScope(Dispatchers.IO).launch {
connectToSocket(bluetoothAdapter, device)
}
setMetadatas(device)
macAddress = device.address
sharedPreferences.edit {
putString("mac_address", macAddress)
}
}
}
}
return START_STICKY return START_STICKY
} }
@@ -2647,15 +2623,15 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
} }
private fun createBluetoothSocket( private fun createBluetoothSocket(
adapter: BluetoothAdapter, device: BluetoothDevice, uuid: ParcelUuid adapter: BluetoothAdapter, device: BluetoothDevice, uuid: ParcelUuid, psm: Int
): BluetoothSocket { ): BluetoothSocket {
val type = 3 // L2CAP val type = 3 // L2CAP
val constructorSpecs = listOf( val constructorSpecs = listOf(
arrayOf(adapter, device, type, true, true, 0x1001, uuid), // A16QPR3 arrayOf(adapter, device, type, true, true, psm, uuid), // A16QPR3
arrayOf(device, type, true, true, 0x1001, uuid), arrayOf(device, type, true, true, psm, uuid),
arrayOf(device, type, 1, true, true, 0x1001, uuid), arrayOf(device, type, 1, true, true, psm, uuid),
arrayOf(type, 1, true, true, device, 0x1001, uuid), arrayOf(type, 1, true, true, device, psm, uuid),
arrayOf(type, true, true, device, 0x1001, uuid) arrayOf(type, true, true, device, psm, uuid)
) )
val constructors = BluetoothSocket::class.java.declaredConstructors val constructors = BluetoothSocket::class.java.declaredConstructors
@@ -2701,7 +2677,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
val uuid: ParcelUuid = ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a") val uuid: ParcelUuid = ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a")
// if (!isConnectedLocally) { // if (!isConnectedLocally) {
socket = try { socket = try {
createBluetoothSocket(adapter, device, uuid) createBluetoothSocket(adapter, device, uuid, 4097)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Failed to create BluetoothSocket: ${e.message}") Log.e(TAG, "Failed to create BluetoothSocket: ${e.message}")
showSocketConnectionFailureNotification("Failed to create Bluetooth socket: ${e.localizedMessage}") showSocketConnectionFailureNotification("Failed to create Bluetooth socket: ${e.localizedMessage}")
@@ -2710,20 +2686,22 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
try { try {
runBlocking { runBlocking {
withTimeout(5000L) { withTimeout(5000.milliseconds) {
try { try {
socket.connect() socket.connect()
// isConnectedLocally = true // isConnectedLocally = true
this@AirPodsService.device = device this@AirPodsService.device = device
BluetoothConnectionManager.setCurrentConnection(socket, device)
val xposedRemotePref = XposedRemotePrefProvider.create() val xposedRemotePref = XposedRemotePrefProvider.create()
if (xposedRemotePref.getBoolean("vendor_id_hook", false)) { val attSocket = if (xposedRemotePref.getBoolean("vendor_id_hook", false)) {
if (attManager == null) { createBluetoothSocket(
attManager = ATTManager(adapter, device) adapter,
attManager!!.connect() device,
} ParcelUuid.fromString("00000000-0000-0000-0000-000000000000"),
} 31
)
} else null
attSocket?.connect()
BluetoothConnectionManager.setCurrentConnection(socket, attSocket)
// Create AirPodsInstance from stored config if available // Create AirPodsInstance from stored config if available
if (airpodsInstance == null && config.airpodsModelNumber.isNotEmpty()) { if (airpodsInstance == null && config.airpodsModelNumber.isNotEmpty()) {
@@ -2928,7 +2906,8 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
socket.close() socket.close()
// isConnectedLocally = false // isConnectedLocally = false
aacpManager.disconnected() aacpManager.disconnected()
attManager?.disconnect() BluetoothConnectionManager.getATTSocket()?.close()
BluetoothConnectionManager.setCurrentConnection(null, null)
updateNotificationContent(false) updateNotificationContent(false)
sendBroadcast(Intent(AirPodsNotifications.AIRPODS_DISCONNECTED).apply { sendBroadcast(Intent(AirPodsNotifications.AIRPODS_DISCONNECTED).apply {
setPackage(packageName) setPackage(packageName)