Compare commits

...

7 Commits

Author SHA1 Message Date
Kavish Devar 04ef891c4b disable DI hook and dependant functions 2026-03-29 16:28:45 +05:30
Kavish Devar 555a000def add A16QPR3 constructor for ATT
i really need to clean this up
2026-03-29 16:22:16 +05:30
Kavish Devar 45f53d5cd4 actually bring back DI hook 2026-03-29 16:12:12 +05:30
Kavish Devar 1425a7d7cf bump version 2026-03-29 16:07:39 +05:30
Kavish Devar e8204a7750 add A16QPR3 constructor
it's almost as if google is doing this deliberately doing this
2026-03-29 16:07:23 +05:30
Kavish Devar 5fbfda6115 add back DI hook 2026-03-29 16:04:50 +05:30
Kavish Devar a6a284c2ec android: disable DI hook
for CI
2026-03-18 08:52:28 +05:30
7 changed files with 113 additions and 105 deletions
+2 -2
View File
@@ -14,8 +14,8 @@ android {
applicationId = "me.kavishdevar.librepods" applicationId = "me.kavishdevar.librepods"
minSdk = 33 minSdk = 33
targetSdk = 36 targetSdk = 36
versionCode = 9 versionCode = 10
versionName = "0.2.0" versionName = "0.2.0-alpha.2"
} }
buildTypes { buildTypes {
+13 -13
View File
@@ -265,9 +265,9 @@ static bool hookLibrary(const char* libname) {
findSymbolOffset(decompressed, findSymbolOffset(decompressed,
"l2c_fcr_chk_chan_modes"); "l2c_fcr_chk_chan_modes");
uint64_t sdp_offset = // uint64_t sdp_offset =
findSymbolOffset(decompressed, // findSymbolOffset(decompressed,
"BTA_DmSetLocalDiRecord"); // "BTA_DmSetLocalDiRecord");
if (chk_offset) { if (chk_offset) {
void* target = void* target =
@@ -280,16 +280,16 @@ static bool hookLibrary(const char* libname) {
LOGI("Hooked l2c_fcr_chk_chan_modes"); LOGI("Hooked l2c_fcr_chk_chan_modes");
} }
if (sdp_offset) { // if (sdp_offset) {
void* target = // void* target =
reinterpret_cast<void*>(base + sdp_offset); // reinterpret_cast<void*>(base + sdp_offset);
//
hook_func(target, // hook_func(target,
(void*)fake_BTA_DmSetLocalDiRecord, // (void*)fake_BTA_DmSetLocalDiRecord,
(void**)&original_BTA_DmSetLocalDiRecord); // (void**)&original_BTA_DmSetLocalDiRecord);
//
LOGI("Hooked BTA_DmSetLocalDiRecord"); // LOGI("Hooked BTA_DmSetLocalDiRecord");
} // }
return true; return true;
} }
@@ -99,7 +99,7 @@ fun AccessibilitySettingsScreen(navController: NavController) {
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 aacpManager = remember { ServiceManager.getService()?.aacpManager } val aacpManager = remember { ServiceManager.getService()?.aacpManager }
val isSdpOffsetAvailable = remember { mutableStateOf(true) } // always available rn, for testing without radare val isSdpOffsetAvailable = remember { mutableStateOf(false) } // always available rn, for testing without radare
// remember { mutableStateOf(RadareOffsetFinder.isSdpOffsetAvailable()) } // remember { mutableStateOf(RadareOffsetFinder.isSdpOffsetAvailable()) }
val trackColor = if (isDarkTheme) Color(0xFFB3B3B3) else Color(0xFF929491) val trackColor = if (isDarkTheme) Color(0xFFB3B3B3) else Color(0xFF929491)
@@ -267,10 +267,10 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService,
} }
// val actAsAppleDeviceHookEnabled = RadareOffsetFinder.isSdpOffsetAvailable() // val actAsAppleDeviceHookEnabled = RadareOffsetFinder.isSdpOffsetAvailable()
// if (actAsAppleDeviceHookEnabled) { // if (actAsAppleDeviceHookEnabled) {
item(key = "spacer_hearing_health") { Spacer(modifier = Modifier.height(32.dp)) } // item(key = "spacer_hearing_health") { Spacer(modifier = Modifier.height(32.dp)) }
item(key = "hearing_health") { // item(key = "hearing_health") {
HearingHealthSettings(navController = navController) // HearingHealthSettings(navController = navController)
} // }
// } // }
if (capabilities.contains(Capability.LISTENING_MODE)) { if (capabilities.contains(Capability.LISTENING_MODE)) {
@@ -90,7 +90,7 @@ fun TransparencySettingsScreen(navController: NavController) {
val verticalScrollState = rememberScrollState() val verticalScrollState = rememberScrollState()
val attManager = ServiceManager.getService()?.attManager ?: return val attManager = ServiceManager.getService()?.attManager ?: return
val aacpManager = remember { ServiceManager.getService()?.aacpManager } val aacpManager = remember { ServiceManager.getService()?.aacpManager }
val isSdpOffsetAvailable = remember { mutableStateOf(true) } // always available rn, for testing without radare val isSdpOffsetAvailable = remember { mutableStateOf(false) } // always available rn, for testing without radare
// remember { mutableStateOf(RadareOffsetFinder.isSdpOffsetAvailable()) } // remember { mutableStateOf(RadareOffsetFinder.isSdpOffsetAvailable()) }
val trackColor = if (isDarkTheme) Color(0xFFB3B3B3) else Color(0xFF929491) val trackColor = if (isDarkTheme) Color(0xFFB3B3B3) else Color(0xFF929491)
@@ -29,6 +29,7 @@ import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.app.Service import android.app.Service
import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetManager
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothHeadset import android.bluetooth.BluetoothHeadset
import android.bluetooth.BluetoothManager import android.bluetooth.BluetoothManager
@@ -252,9 +253,10 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
if (device.connectionState == "Disconnected" && !config.bleOnlyMode) { if (device.connectionState == "Disconnected" && !config.bleOnlyMode) {
Log.d(TAG, "Seems no device has taken over, we will.") Log.d(TAG, "Seems no device has taken over, we will.")
val bluetoothManager = getSystemService(BluetoothManager::class.java) val bluetoothManager = getSystemService(BluetoothManager::class.java)
val bluetoothDevice = bluetoothManager.adapter.getRemoteDevice(sharedPreferences.getString( val bluetoothAdapter = bluetoothManager.adapter
val bluetoothDevice = bluetoothAdapter.getRemoteDevice(sharedPreferences.getString(
"mac_address", "") ?: "") "mac_address", "") ?: "")
connectToSocket(bluetoothDevice) connectToSocket(bluetoothAdapter, bluetoothDevice)
} }
Log.d(TAG, "Device status changed") Log.d(TAG, "Device status changed")
if (isConnectedLocally) return if (isConnectedLocally) return
@@ -607,7 +609,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
// if ((CrossDevice.isAvailable && !isConnectedLocally && earDetectionNotification.status.contains(0x00)) || leAvailableForAudio) CoroutineScope( // if ((CrossDevice.isAvailable && !isConnectedLocally && earDetectionNotification.status.contains(0x00)) || leAvailableForAudio) CoroutineScope(
if (leAvailableForAudio) CoroutineScope( if (leAvailableForAudio) CoroutineScope(
Dispatchers.IO).launch { Dispatchers.IO).launch {
takeOver("call") takeOver("call")
} }
isInCall = true isInCall = true
} }
@@ -665,17 +667,18 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
// Log.d("AirPodsCrossDevice", CrossDevice.isAvailable.toString()) // Log.d("AirPodsCrossDevice", CrossDevice.isAvailable.toString())
// if (!CrossDevice.isAvailable) { // if (!CrossDevice.isAvailable) {
Log.d(TAG, "${config.deviceName} connected") Log.d(TAG, "${config.deviceName} connected")
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
connectToSocket(device!!) val bluetoothManager = getSystemService(BluetoothManager::class.java)
} connectToSocket(bluetoothManager.adapter, device!!)
Log.d(TAG, "Setting metadata") }
setMetadatas(device!!) Log.d(TAG, "Setting metadata")
isConnectedLocally = true setMetadatas(device!!)
macAddress = device!!.address isConnectedLocally = true
sharedPreferences.edit { macAddress = device!!.address
putString("mac_address", macAddress) sharedPreferences.edit {
} putString("mac_address", macAddress)
}
// } // }
} else if (intent?.action == AirPodsNotifications.AIRPODS_DISCONNECTED) { } else if (intent?.action == AirPodsNotifications.AIRPODS_DISCONNECTED) {
@@ -688,7 +691,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
} }
} }
} }
val showIslandReceiver = object: BroadcastReceiver() { val showIslandReceiver = object: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action == "me.kavishdevar.librepods.cross_device_island") { if (intent?.action == "me.kavishdevar.librepods.cross_device_island") {
showIsland(this@AirPodsService, batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level!!.coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level!!)) showIsland(this@AirPodsService, batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level!!.coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level!!))
@@ -743,14 +746,14 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
val connectedDevices = proxy.connectedDevices val connectedDevices = proxy.connectedDevices
if (connectedDevices.isNotEmpty()) { if (connectedDevices.isNotEmpty()) {
// if (!CrossDevice.isAvailable) { // if (!CrossDevice.isAvailable) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
connectToSocket(device) connectToSocket(bluetoothAdapter, device)
} }
setMetadatas(device) setMetadatas(device)
macAddress = device.address macAddress = device.address
sharedPreferences.edit { sharedPreferences.edit {
putString("mac_address", macAddress) putString("mac_address", macAddress)
} }
// } // }
this@AirPodsService.sendBroadcast( this@AirPodsService.sendBroadcast(
Intent(AirPodsNotifications.AIRPODS_CONNECTED) Intent(AirPodsNotifications.AIRPODS_CONNECTED)
@@ -1555,7 +1558,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
.setContentText("Unable to connect to AirPods over L2CAP") .setContentText("Unable to connect to AirPods over L2CAP")
.setStyle(NotificationCompat.BigTextStyle() .setStyle(NotificationCompat.BigTextStyle()
.bigText("Your AirPods are connected via Bluetooth, but LibrePods couldn't connect to AirPods using L2CAP. " + .bigText("Your AirPods are connected via Bluetooth, but LibrePods couldn't connect to AirPods using L2CAP. " +
"Error: $errorMessage")) "Error: $errorMessage"))
.setContentIntent(pendingIntent) .setContentIntent(pendingIntent)
.setCategory(Notification.CATEGORY_ERROR) .setCategory(Notification.CATEGORY_ERROR)
.setPriority(NotificationCompat.PRIORITY_HIGH) .setPriority(NotificationCompat.PRIORITY_HIGH)
@@ -2085,56 +2088,56 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
device.METADATA_MAIN_ICON, device.METADATA_MAIN_ICON,
resToUri(instance.model.budCaseRes).toString().toByteArray() resToUri(instance.model.budCaseRes).toString().toByteArray()
) && ) &&
SystemApisUtils.setMetadata( SystemApisUtils.setMetadata(
device, device,
device.METADATA_MODEL_NAME, device.METADATA_MODEL_NAME,
instance.model.name.toByteArray() instance.model.name.toByteArray()
) && ) &&
SystemApisUtils.setMetadata( SystemApisUtils.setMetadata(
device, device,
device.METADATA_DEVICE_TYPE, device.METADATA_DEVICE_TYPE,
device.DEVICE_TYPE_UNTETHERED_HEADSET.toByteArray() device.DEVICE_TYPE_UNTETHERED_HEADSET.toByteArray()
) && ) &&
SystemApisUtils.setMetadata( SystemApisUtils.setMetadata(
device, device,
device.METADATA_UNTETHERED_CASE_ICON, device.METADATA_UNTETHERED_CASE_ICON,
resToUri(instance.model.caseRes).toString().toByteArray() resToUri(instance.model.caseRes).toString().toByteArray()
) && ) &&
SystemApisUtils.setMetadata( SystemApisUtils.setMetadata(
device, device,
device.METADATA_UNTETHERED_RIGHT_ICON, device.METADATA_UNTETHERED_RIGHT_ICON,
resToUri(instance.model.rightBudsRes).toString().toByteArray() resToUri(instance.model.rightBudsRes).toString().toByteArray()
) && ) &&
SystemApisUtils.setMetadata( SystemApisUtils.setMetadata(
device, device,
device.METADATA_UNTETHERED_LEFT_ICON, device.METADATA_UNTETHERED_LEFT_ICON,
resToUri(instance.model.leftBudsRes).toString().toByteArray() resToUri(instance.model.leftBudsRes).toString().toByteArray()
) && ) &&
SystemApisUtils.setMetadata( SystemApisUtils.setMetadata(
device, device,
device.METADATA_MANUFACTURER_NAME, device.METADATA_MANUFACTURER_NAME,
instance.model.manufacturer.toByteArray() instance.model.manufacturer.toByteArray()
) && ) &&
SystemApisUtils.setMetadata( SystemApisUtils.setMetadata(
device, device,
device.METADATA_COMPANION_APP, device.METADATA_COMPANION_APP,
"me.kavishdevar.librepods".toByteArray() "me.kavishdevar.librepods".toByteArray()
) && ) &&
SystemApisUtils.setMetadata( SystemApisUtils.setMetadata(
device, device,
device.METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD, device.METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD,
"20".toByteArray() "20".toByteArray()
) && ) &&
SystemApisUtils.setMetadata( SystemApisUtils.setMetadata(
device, device,
device.METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD, device.METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD,
"20".toByteArray() "20".toByteArray()
) && ) &&
SystemApisUtils.setMetadata( SystemApisUtils.setMetadata(
device, device,
device.METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD, device.METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD,
"20".toByteArray() "20".toByteArray()
) )
Log.d(TAG, "Metadata set: $metadataSet") Log.d(TAG, "Metadata set: $metadataSet")
} else { } else {
Log.w(TAG, "AirPods instance is not of type AirPodsInstance, skipping metadata setting") Log.w(TAG, "AirPods instance is not of type AirPodsInstance, skipping metadata setting")
@@ -2325,7 +2328,9 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
Log.d(TAG, macAddress) Log.d(TAG, macAddress)
// sharedPreferences.edit { putBoolean("CrossDeviceIsAvailable", false) } // sharedPreferences.edit { putBoolean("CrossDeviceIsAvailable", false) }
device = getSystemService(BluetoothManager::class.java).adapter.bondedDevices.find { val bluetoothManager = getSystemService(BluetoothManager::class.java)
val bluetoothAdapter = bluetoothManager.adapter
device = bluetoothAdapter.bondedDevices.find {
it.address == macAddress it.address == macAddress
} }
@@ -2341,7 +2346,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
// Set a temporary connecting state // Set a temporary connecting state
isConnectedLocally = false // Keep as false since we're not actually connecting to L2CAP isConnectedLocally = false // Keep as false since we're not actually connecting to L2CAP
} else { } else {
connectToSocket(device!!) connectToSocket(bluetoothAdapter, device!!)
connectAudio(this, device) connectAudio(this, device)
isConnectedLocally = true isConnectedLocally = true
} }
@@ -2352,9 +2357,10 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
// CrossDevice.isAvailable = false // CrossDevice.isAvailable = false
} }
private fun createBluetoothSocket(device: BluetoothDevice, uuid: ParcelUuid): BluetoothSocket { private fun createBluetoothSocket(adapter: BluetoothAdapter, device: BluetoothDevice, uuid: ParcelUuid): 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(device, type, true, true, 0x1001, uuid), arrayOf(device, type, true, true, 0x1001, uuid),
arrayOf(device, type, 1, true, true, 0x1001, uuid), arrayOf(device, type, 1, true, true, 0x1001, uuid),
arrayOf(type, 1, true, true, device, 0x1001, uuid), arrayOf(type, 1, true, true, device, 0x1001, uuid),
@@ -2390,13 +2396,13 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
} }
@SuppressLint("MissingPermission", "UnspecifiedRegisterReceiverFlag") @SuppressLint("MissingPermission", "UnspecifiedRegisterReceiverFlag")
fun connectToSocket(device: BluetoothDevice, manual: Boolean = false) { fun connectToSocket(adapter: BluetoothAdapter, device: BluetoothDevice, manual: Boolean = false) {
Log.d(TAG, "<LogCollector:Start> Connecting to socket") Log.d(TAG, "<LogCollector:Start> Connecting to socket")
HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;") HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;")
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(device, uuid) createBluetoothSocket(adapter, device, uuid)
} 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}")
@@ -2413,7 +2419,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
BluetoothConnectionManager.setCurrentConnection(socket, device) BluetoothConnectionManager.setCurrentConnection(socket, device)
attManager = ATTManager(device) attManager = ATTManager(adapter, device)
attManager!!.connect() attManager!!.connect()
// Create AirPodsInstance from stored config if available // Create AirPodsInstance from stored config if available
@@ -2812,7 +2818,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
} }
if (device != null) { if (device != null) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
connectToSocket(device!!, manual = true) connectToSocket(bluetoothAdapter, device!!, manual = true)
} }
} }
} }
@@ -24,6 +24,7 @@
package me.kavishdevar.librepods.utils package me.kavishdevar.librepods.utils
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothSocket import android.bluetooth.BluetoothSocket
import android.os.ParcelUuid import android.os.ParcelUuid
@@ -49,7 +50,7 @@ enum class ATTCCCDHandles(val value: Int) {
HEARING_AID(ATTHandles.HEARING_AID.value + 1), HEARING_AID(ATTHandles.HEARING_AID.value + 1),
} }
class ATTManager(private val device: BluetoothDevice) { class ATTManager(private val adapter: BluetoothAdapter, private val device: BluetoothDevice) {
companion object { companion object {
private const val TAG = "ATTManager" private const val TAG = "ATTManager"
@@ -72,7 +73,7 @@ class ATTManager(private val device: BluetoothDevice) {
HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;") HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;")
val uuid = ParcelUuid.fromString("00000000-0000-0000-0000-000000000000") val uuid = ParcelUuid.fromString("00000000-0000-0000-0000-000000000000")
socket = createBluetoothSocket(device, uuid) socket = createBluetoothSocket(adapter, device, uuid)
socket!!.connect() socket!!.connect()
input = socket!!.inputStream input = socket!!.inputStream
output = socket!!.outputStream output = socket!!.outputStream
@@ -195,9 +196,10 @@ class ATTManager(private val device: BluetoothDevice) {
} }
} }
private fun createBluetoothSocket(device: BluetoothDevice, uuid: ParcelUuid): BluetoothSocket { private fun createBluetoothSocket(adapter: BluetoothAdapter, device: BluetoothDevice, uuid: ParcelUuid): BluetoothSocket {
val type = 3 // L2CAP val type = 3 // L2CAP
val constructorSpecs = listOf( val constructorSpecs = listOf(
arrayOf(adapter, device, type, true, 31, uuid),
arrayOf(device, type, true, true, 31, uuid), arrayOf(device, type, true, true, 31, uuid),
arrayOf(device, type, 1, true, true, 31, uuid), arrayOf(device, type, 1, true, true, 31, uuid),
arrayOf(type, 1, true, true, device, 31, uuid), arrayOf(type, 1, true, true, device, 31, uuid),