diff --git a/android/app/src/main/java/me/kavishdevar/librepods/bluetooth/ATTManager.kt b/android/app/src/main/java/me/kavishdevar/librepods/bluetooth/ATTManager.kt index c1ffd7d2..8f851775 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/bluetooth/ATTManager.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/bluetooth/ATTManager.kt @@ -52,13 +52,13 @@ enum class ATTCCCDHandles(val value: Int) { class ATTManager(private val adapter: BluetoothAdapter, private val device: BluetoothDevice) { companion object { - private const val TAG = "ATTManager" - private const val OPCODE_READ_REQUEST: Byte = 0x0A private const val OPCODE_WRITE_REQUEST: Byte = 0x12 private const val OPCODE_HANDLE_VALUE_NTF: Byte = 0x1B } - + private val id = System.identityHashCode(this) + @Suppress("PrivatePropertyName") + private val TAG = "ATTManager[$id]" var socket: BluetoothSocket? = null private var input: InputStream? = null private var output: OutputStream? = null @@ -72,16 +72,25 @@ class ATTManager(private val adapter: BluetoothAdapter, private val device: Blue fun connect() { val uuid = ParcelUuid.fromString("00000000-0000-0000-0000-000000000000") - try { - socket = createBluetoothSocket(adapter, device, uuid) - } catch (e: Exception) { - Log.w(TAG, "Failed to create socket") + if (socket == null) { + Log.d(TAG, "Socket doesn't exist, creating") + try { + socket = createBluetoothSocket(adapter, device, uuid) + } catch (e: Exception) { + Log.w(TAG, "Failed to create socket") + e.printStackTrace() + return + } } - try { - socket!!.connect() - } catch (e: Exception) { - Log.w(TAG, "ATT socket failed to connect") - return + if (socket?.isConnected != true) { + Log.d(TAG, "Connection to socket") + try { + socket!!.connect() + } catch (e: Exception) { + Log.w(TAG, "ATT socket failed to connect") + e.printStackTrace() + return + } } input = socket!!.inputStream output = socket!!.outputStream @@ -113,6 +122,10 @@ class ATTManager(private val adapter: BluetoothAdapter, private val device: Blue } } } + if (output != null) { + Log.d(TAG, "sending read req for hearing aid declaration") + output?.write(byteArrayOf(0x0A, 0x29, 0x00)) + } } fun disconnect() { @@ -216,11 +229,11 @@ class ATTManager(private val adapter: BluetoothAdapter, private val device: Blue ) val constructors = BluetoothSocket::class.java.declaredConstructors - Log.d("ATTManager", "BluetoothSocket has ${constructors.size} constructors:") + Log.d(TAG, "BluetoothSocket has ${constructors.size} constructors:") constructors.forEachIndexed { index, constructor -> val params = constructor.parameterTypes.joinToString(", ") { it.simpleName } - Log.d("ATTManager", "Constructor $index: ($params)") + Log.d(TAG, "Constructor $index: ($params)") } var lastException: Exception? = null @@ -228,7 +241,7 @@ class ATTManager(private val adapter: BluetoothAdapter, private val device: Blue for ((index, params) in constructorSpecs.withIndex()) { try { - Log.d("ATTManager", "Trying constructor signature #${index + 1}") + Log.d(TAG, "Trying constructor signature #${index + 1}") attemptedConstructors++ val paramTypes = params.map { it::class.javaPrimitiveType ?: it::class.java }.toTypedArray() @@ -237,13 +250,13 @@ class ATTManager(private val adapter: BluetoothAdapter, private val device: Blue return constructor.newInstance(*params) as BluetoothSocket } catch (e: Exception) { - Log.e("ATTManager", "Constructor signature #${index + 1} failed: ${e.message}") + Log.e(TAG, "Constructor signature #${index + 1} failed: ${e.message}") lastException = e } } val errorMessage = "Failed to create BluetoothSocket after trying $attemptedConstructors constructor signatures" - Log.e("ATTManager", errorMessage) + Log.e(TAG, errorMessage) throw lastException ?: IllegalStateException(errorMessage) } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/presentation/viewmodel/AirPodsViewModel.kt b/android/app/src/main/java/me/kavishdevar/librepods/presentation/viewmodel/AirPodsViewModel.kt index 45752ecc..e9ed96cf 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/presentation/viewmodel/AirPodsViewModel.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/presentation/viewmodel/AirPodsViewModel.kt @@ -530,7 +530,9 @@ class AirPodsViewModel( } viewModelScope.launch(Dispatchers.IO) { try { - service.attManager?.connect() + if (service.attManager?.socket?.isConnected != true) { + service.attManager?.connect() + } while (service.attManager?.socket?.isConnected != true) { delay(250) } @@ -545,21 +547,28 @@ class AirPodsViewModel( viewModelScope.launch(Dispatchers.IO) { val loudSoundReduction = runCatching { service.attManager?.read(ATTHandles.LOUD_SOUND_REDUCTION) }.getOrNull() + val loudSoundReductionEnabled = loudSoundReduction?.size?.let { + if (it > 0) { + loudSoundReduction[0].toInt() == 1 + } else false + } val transparencyData = runCatching { service.attManager?.read(ATTHandles.TRANSPARENCY) }.getOrNull()?: byteArrayOf() - val hearingAid = + val hearingAidData = runCatching { service.attManager?.read(ATTHandles.HEARING_AID) }.getOrNull()?: byteArrayOf() _uiState.value = _uiState.value.copy( - loudSoundReductionEnabled = loudSoundReduction?.get(0)?.toInt() == 0x01, + loudSoundReductionEnabled = loudSoundReductionEnabled == true, transparencyData = transparencyData, - hearingAidData = hearingAid + hearingAidData = hearingAidData ) } } fun observeATT() { viewModelScope.launch(Dispatchers.IO) { - service.attManager?.connect() + if (service.attManager?.socket?.isConnected != true) { + service.attManager?.connect() + } while (service.attManager?.socket?.isConnected != true) { delay(1000) } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt index 0b074a83..0f15d62a 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt @@ -1019,7 +1019,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList ) // Store in SharedPreferences sharedPreferences.edit { - putString("airpods_name", deviceInformation.name) + putString("name", deviceInformation.name) putString("airpods_model_number", deviceInformation.modelNumber) putString("airpods_manufacturer", deviceInformation.manufacturer) putString("airpods_serial_number", deviceInformation.serialNumber) @@ -2388,16 +2388,27 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList ?.getString("name", bluetoothDevice?.name) if (bluetoothDevice != null && !action.isNullOrEmpty()) { Log.d(TAG, "Received bluetooth connection broadcast: action=$action") + val uuid = ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a") + if (BluetoothDevice.ACTION_ACL_CONNECTED == action) { - val uuid = ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a") - bluetoothDevice.fetchUuidsWithSdp() - if (bluetoothDevice.uuids != null) { - if (bluetoothDevice.uuids.contains(uuid)) { - val intent = Intent(AirPodsNotifications.AIRPODS_CONNECTION_DETECTED) - intent.putExtra("name", name) - intent.putExtra("device", bluetoothDevice) - context?.sendBroadcast(intent) - } + if (bluetoothDevice.uuids?.contains(uuid) == true) { + val intent = Intent(AirPodsNotifications.AIRPODS_CONNECTION_DETECTED) + intent.putExtra("name", name) + intent.putExtra("device", bluetoothDevice) + context?.sendBroadcast(intent) + } else { + bluetoothDevice.fetchUuidsWithSdp() + } + } else if ("android.bluetooth.device.action.UUID" == action) { + val savedMac = context?.getSharedPreferences("settings", MODE_PRIVATE) + ?.getString("mac_address", "") ?: "" + val matchedByMac = savedMac.isNotEmpty() && bluetoothDevice.address == savedMac + val matchedByUuid = bluetoothDevice.uuids?.contains(uuid) == true + if (matchedByUuid || matchedByMac) { + val intent = Intent(AirPodsNotifications.AIRPODS_CONNECTION_DETECTED) + intent.putExtra("name", name) + intent.putExtra("device", bluetoothDevice) + context?.sendBroadcast(intent) } } } @@ -2421,6 +2432,32 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList 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 } @@ -2682,8 +2719,10 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList BluetoothConnectionManager.setCurrentConnection(socket, device) val xposedRemotePref = XposedRemotePrefProvider.create() if (xposedRemotePref.getBoolean("vendor_id_hook", false)) { - attManager = ATTManager(adapter, device) - attManager!!.connect() + if (attManager == null) { + attManager = ATTManager(adapter, device) + attManager!!.connect() + } } // Create AirPodsInstance from stored config if available