mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-05-31 13:33:02 +00:00
android: fix rework ATT connection
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user