android(fix): do not require phone's MAC for service start (#253)

This makes the app run without issues on OxygenOS/ColorOS16 without root.

* android(fix): add missing HEAD_GESTURES capability on app2

* android(fix): catch att initial read exceptions in toggle

* android(refactor): remove navcontroller from head gestures screen

* android(fix): do not crash when connected devices list is sent empty

had never seen this before, this was the first time airpods saying zero connected devices

* android(fix): do not crash if phone's MAC not available

also removed crossdevice code

* android: skip sdp hook check if setup skipped
This commit is contained in:
Kavish Devar
2025-11-19 23:20:24 +05:30
committed by GitHub
parent 1dbb36a2aa
commit 141f1e7604
7 changed files with 110 additions and 61 deletions

View File

@@ -381,7 +381,7 @@ fun Main() {
TroubleshootingScreen(navController) TroubleshootingScreen(navController)
} }
composable("head_tracking") { composable("head_tracking") {
HeadTrackingScreen(navController) HeadTrackingScreen()
} }
composable("onboarding") { composable("onboarding") {
Onboarding(navController, context) Onboarding(navController, context)

View File

@@ -472,7 +472,12 @@ fun StyledToggle(
val attManager = ServiceManager.getService()?.attManager ?: return val attManager = ServiceManager.getService()?.attManager ?: return
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 checkedValue = attManager.read(attHandle).getOrNull(0)?.toInt() val checkedValue = try {
attManager.read(attHandle).getOrNull(0)?.toInt()
} catch (e: Exception) {
Log.w("StyledToggle", "Error reading initial value for $label: ${e.message}")
null
} ?: 0
var checked by remember { mutableStateOf(checkedValue !=0) } var checked by remember { mutableStateOf(checkedValue !=0) }
var backgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } var backgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) }
val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500)) val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500))

View File

@@ -16,6 +16,9 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
// this is absolutely unnecessary, why did I make this. a simple toggle would've sufficed
@file:OptIn(ExperimentalEncodingApi::class) @file:OptIn(ExperimentalEncodingApi::class)
package me.kavishdevar.librepods.screens package me.kavishdevar.librepods.screens
@@ -83,7 +86,6 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.kyant.backdrop.backdrops.layerBackdrop import com.kyant.backdrop.backdrops.layerBackdrop
import com.kyant.backdrop.backdrops.rememberLayerBackdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop
import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.hazeSource
@@ -108,7 +110,7 @@ import kotlin.random.Random
@RequiresApi(Build.VERSION_CODES.Q) @RequiresApi(Build.VERSION_CODES.Q)
@OptIn(ExperimentalMaterial3Api::class, ExperimentalAnimationApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalAnimationApi::class)
@Composable @Composable
fun HeadTrackingScreen(navController: NavController) { fun HeadTrackingScreen() {
DisposableEffect(Unit) { DisposableEffect(Unit) {
ServiceManager.getService()?.startHeadTracking() ServiceManager.getService()?.startHeadTracking()
onDispose { onDispose {
@@ -743,5 +745,5 @@ private fun AccelerationPlot() {
@Preview @Preview
@Composable @Composable
fun HeadTrackingScreenPreview() { fun HeadTrackingScreenPreview() {
HeadTrackingScreen(navController = NavController(LocalContext.current)) HeadTrackingScreen()
} }

View File

@@ -93,8 +93,8 @@ import me.kavishdevar.librepods.utils.AirPodsInstance
import me.kavishdevar.librepods.utils.AirPodsModels import me.kavishdevar.librepods.utils.AirPodsModels
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
import me.kavishdevar.librepods.utils.CrossDevicePackets //import me.kavishdevar.librepods.utils.CrossDevicePackets
import me.kavishdevar.librepods.utils.GestureDetector import me.kavishdevar.librepods.utils.GestureDetector
import me.kavishdevar.librepods.utils.HeadTracking import me.kavishdevar.librepods.utils.HeadTracking
import me.kavishdevar.librepods.utils.IslandType import me.kavishdevar.librepods.utils.IslandType
@@ -193,7 +193,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
var leftLongPressAction: StemAction = StemAction.defaultActions[StemPressType.LONG_PRESS]!!, var leftLongPressAction: StemAction = StemAction.defaultActions[StemPressType.LONG_PRESS]!!,
var rightLongPressAction: StemAction = StemAction.defaultActions[StemPressType.LONG_PRESS]!!, var rightLongPressAction: StemAction = StemAction.defaultActions[StemPressType.LONG_PRESS]!!,
var cameraAction: AACPManager.Companion.StemPressType? = null, var cameraAction: StemPressType? = null,
// AirPods device information // AirPods device information
var airpodsName: String = "", var airpodsName: String = "",
@@ -207,6 +207,9 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
var airpodsVersion3: String = "", var airpodsVersion3: String = "",
var airpodsHardwareRevision: String = "", var airpodsHardwareRevision: String = "",
var airpodsUpdaterIdentifier: String = "", var airpodsUpdaterIdentifier: String = "",
// phone's mac, needed for tipi
var selfMacAddress: String = ""
) )
private lateinit var config: ServiceConfig private lateinit var config: ServiceConfig
@@ -368,9 +371,29 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
sharedPreferences.registerOnSharedPreferenceChangeListener(this) sharedPreferences.registerOnSharedPreferenceChangeListener(this)
val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "settings", "get", "secure", "bluetooth_address")) localMac = config.selfMacAddress
val output = process.inputStream.bufferedReader().use { it.readLine() } if (localMac.isEmpty()) {
localMac = output.trim() localMac = try {
val process = Runtime.getRuntime().exec(
arrayOf("su", "-c", "settings get secure bluetooth_address")
)
val exitCode = process.waitFor()
if (exitCode == 0) {
process.inputStream.bufferedReader().use { it.readLine()?.trim().orEmpty() }
} else {
""
}
} catch (e: Exception) {
Log.e(TAG, "Error retrieving local MAC address: ${e.message}. We probably aren't rooted.")
""
}
config.selfMacAddress = localMac
sharedPreferences.edit {
putString("self_mac_address", localMac)
}
}
ServiceManager.setService(this) ServiceManager.setService(this)
startForegroundNotification() startForegroundNotification()
@@ -556,11 +579,11 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
MODE_PRIVATE MODE_PRIVATE
) )
) )
Log.d(TAG, "Initializing CrossDevice") // Log.d(TAG, "Initializing CrossDevice")
CoroutineScope(Dispatchers.IO).launch { // CoroutineScope(Dispatchers.IO).launch {
CrossDevice.init(this@AirPodsService) // CrossDevice.init(this@AirPodsService)
Log.d(TAG, "CrossDevice initialized") // Log.d(TAG, "CrossDevice initialized")
} // }
sharedPreferences = getSharedPreferences("settings", MODE_PRIVATE) sharedPreferences = getSharedPreferences("settings", MODE_PRIVATE)
macAddress = sharedPreferences.getString("mac_address", "") ?: "" macAddress = sharedPreferences.getString("mac_address", "") ?: ""
@@ -573,7 +596,8 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
when (state) { when (state) {
TelephonyManager.CALL_STATE_RINGING -> { TelephonyManager.CALL_STATE_RINGING -> {
val leAvailableForAudio = bleManager.getMostRecentStatus()?.isLeftInEar == true || bleManager.getMostRecentStatus()?.isRightInEar == true val leAvailableForAudio = bleManager.getMostRecentStatus()?.isLeftInEar == true || bleManager.getMostRecentStatus()?.isRightInEar == true
if ((CrossDevice.isAvailable && !isConnectedLocally && earDetectionNotification.status.contains(0x00)) || leAvailableForAudio) CoroutineScope(Dispatchers.IO).launch { // if ((CrossDevice.isAvailable && !isConnectedLocally && earDetectionNotification.status.contains(0x00)) || leAvailableForAudio) CoroutineScope(Dispatchers.IO).launch {
if (leAvailableForAudio) runBlocking {
takeOver("call") takeOver("call")
} }
if (config.headGestures) { if (config.headGestures) {
@@ -583,7 +607,8 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
} }
TelephonyManager.CALL_STATE_OFFHOOK -> { TelephonyManager.CALL_STATE_OFFHOOK -> {
val leAvailableForAudio = bleManager.getMostRecentStatus()?.isLeftInEar == true || bleManager.getMostRecentStatus()?.isRightInEar == true val leAvailableForAudio = bleManager.getMostRecentStatus()?.isLeftInEar == true || bleManager.getMostRecentStatus()?.isRightInEar == true
if ((CrossDevice.isAvailable && !isConnectedLocally && earDetectionNotification.status.contains(0x00)) || leAvailableForAudio) CoroutineScope( // if ((CrossDevice.isAvailable && !isConnectedLocally && earDetectionNotification.status.contains(0x00)) || leAvailableForAudio) CoroutineScope(
if (leAvailableForAudio) CoroutineScope(
Dispatchers.IO).launch { Dispatchers.IO).launch {
takeOver("call") takeOver("call")
} }
@@ -641,8 +666,8 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
sharedPreferences.edit { putString("name", config.deviceName) } sharedPreferences.edit { putString("name", config.deviceName) }
} }
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!!) connectToSocket(device!!)
@@ -654,7 +679,8 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
sharedPreferences.edit { sharedPreferences.edit {
putString("mac_address", macAddress) putString("mac_address", macAddress)
} }
} // }
} else if (intent?.action == AirPodsNotifications.AIRPODS_DISCONNECTED) { } else if (intent?.action == AirPodsNotifications.AIRPODS_DISCONNECTED) {
device = null device = null
isConnectedLocally = false isConnectedLocally = false
@@ -719,7 +745,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
if (profile == BluetoothProfile.A2DP) { if (profile == BluetoothProfile.A2DP) {
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(device)
} }
@@ -728,7 +754,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
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)
) )
@@ -745,9 +771,9 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
} }
} }
if (!isConnectedLocally && !CrossDevice.isAvailable) { // if (!isConnectedLocally && !CrossDevice.isAvailable) {
clearPacketLogs() // clearPacketLogs()
} // }
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
bleManager.startScanning() bleManager.startScanning()
@@ -819,8 +845,8 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
.getString("name", device?.name), .getString("name", device?.name),
batteryNotification.getBattery() batteryNotification.getBattery()
) )
CrossDevice.sendRemotePacket(batteryInfo) // CrossDevice.sendRemotePacket(batteryInfo)
CrossDevice.batteryBytes = batteryInfo // CrossDevice.batteryBytes = batteryInfo
for (battery in batteryNotification.getBattery()) { for (battery in batteryNotification.getBattery()) {
Log.d( Log.d(
@@ -1229,7 +1255,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
leftLongPressAction = StemAction.fromString(sharedPreferences.getString("left_long_press_action", "CYCLE_NOISE_CONTROL_MODES") ?: "CYCLE_NOISE_CONTROL_MODES")!!, leftLongPressAction = StemAction.fromString(sharedPreferences.getString("left_long_press_action", "CYCLE_NOISE_CONTROL_MODES") ?: "CYCLE_NOISE_CONTROL_MODES")!!,
rightLongPressAction = StemAction.fromString(sharedPreferences.getString("right_long_press_action", "DIGITAL_ASSISTANT") ?: "DIGITAL_ASSISTANT")!!, rightLongPressAction = StemAction.fromString(sharedPreferences.getString("right_long_press_action", "DIGITAL_ASSISTANT") ?: "DIGITAL_ASSISTANT")!!,
cameraAction = sharedPreferences.getString("camera_action", null)?.let { AACPManager.Companion.StemPressType.valueOf(it) }, cameraAction = sharedPreferences.getString("camera_action", null)?.let { StemPressType.valueOf(it) },
// AirPods device information // AirPods device information
airpodsName = sharedPreferences.getString("airpods_name", "") ?: "", airpodsName = sharedPreferences.getString("airpods_name", "") ?: "",
@@ -1243,6 +1269,8 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
airpodsVersion3 = sharedPreferences.getString("airpods_version3", "") ?: "", airpodsVersion3 = sharedPreferences.getString("airpods_version3", "") ?: "",
airpodsHardwareRevision = sharedPreferences.getString("airpods_hardware_revision", "") ?: "", airpodsHardwareRevision = sharedPreferences.getString("airpods_hardware_revision", "") ?: "",
airpodsUpdaterIdentifier = sharedPreferences.getString("airpods_updater_identifier", "") ?: "", airpodsUpdaterIdentifier = sharedPreferences.getString("airpods_updater_identifier", "") ?: "",
selfMacAddress = sharedPreferences.getString("self_mac_address", "") ?: ""
) )
} }
@@ -1251,6 +1279,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
when(key) { when(key) {
"name" -> config.deviceName = preferences.getString(key, "AirPods") ?: "AirPods" "name" -> config.deviceName = preferences.getString(key, "AirPods") ?: "AirPods"
"mac_address" -> macAddress = preferences.getString(key, "") ?: ""
"automatic_ear_detection" -> config.earDetectionEnabled = preferences.getBoolean(key, true) "automatic_ear_detection" -> config.earDetectionEnabled = preferences.getBoolean(key, true)
"conversational_awareness_pause_music" -> config.conversationalAwarenessPauseMusic = preferences.getBoolean(key, false) "conversational_awareness_pause_music" -> config.conversationalAwarenessPauseMusic = preferences.getBoolean(key, false)
"show_phone_battery_in_widget" -> { "show_phone_battery_in_widget" -> {
@@ -1323,7 +1352,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
)!! )!!
setupStemActions() setupStemActions()
} }
"camera_action" -> config.cameraAction = preferences.getString(key, null)?.let { AACPManager.Companion.StemPressType.valueOf(it) } "camera_action" -> config.cameraAction = preferences.getString(key, null)?.let { StemPressType.valueOf(it) }
// AirPods device information // AirPods device information
"airpods_name" -> config.airpodsName = preferences.getString(key, "") ?: "" "airpods_name" -> config.airpodsName = preferences.getString(key, "") ?: ""
@@ -1337,10 +1366,8 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
"airpods_version3" -> config.airpodsVersion3 = preferences.getString(key, "") ?: "" "airpods_version3" -> config.airpodsVersion3 = preferences.getString(key, "") ?: ""
"airpods_hardware_revision" -> config.airpodsHardwareRevision = preferences.getString(key, "") ?: "" "airpods_hardware_revision" -> config.airpodsHardwareRevision = preferences.getString(key, "") ?: ""
"airpods_updater_identifier" -> config.airpodsUpdaterIdentifier = preferences.getString(key, "") ?: "" "airpods_updater_identifier" -> config.airpodsUpdaterIdentifier = preferences.getString(key, "") ?: ""
}
if (key == "mac_address") { "self_mac_address" -> config.selfMacAddress = preferences.getString(key, "") ?: ""
macAddress = preferences.getString(key, "") ?: ""
} }
} }
@@ -2096,7 +2123,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
SystemApisUtils.setMetadata( SystemApisUtils.setMetadata(
device, device,
device.METADATA_COMPANION_APP, device.METADATA_COMPANION_APP,
"me.kavisdevar.librepods".toByteArray() "me.kavishdevar.librepods".toByteArray()
) && ) &&
SystemApisUtils.setMetadata( SystemApisUtils.setMetadata(
device, device,
@@ -2266,14 +2293,19 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
return return
} }
if (CrossDevice.isAvailable) { // if (CrossDevice.isAvailable) {
Log.d(TAG, "CrossDevice is available, continuing") // Log.d(TAG, "CrossDevice is available, continuing")
} // }
else if (bleManager.getMostRecentStatus()?.isLeftInEar == true || bleManager.getMostRecentStatus()?.isRightInEar == true) { // else if (bleManager.getMostRecentStatus()?.isLeftInEar == true || bleManager.getMostRecentStatus()?.isRightInEar == true) {
Log.d(TAG, "At least one AirPod is in ear, continuing") // Log.d(TAG, "At least one AirPod is in ear, continuing")
} // }
else { // else {
Log.d(TAG, "CrossDevice not available and AirPods not in ear, skipping") // Log.d(TAG, "CrossDevice not available and AirPods not in ear, skipping")
// return
// }
if (bleManager.getMostRecentStatus()?.isLeftInEar == false && bleManager.getMostRecentStatus()?.isRightInEar == false) {
Log.d(TAG, "Both AirPods are out of ear, not taking over audio")
return return
} }
@@ -2312,10 +2344,10 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
} }
Log.d(TAG, "Taking over audio") Log.d(TAG, "Taking over audio")
CrossDevice.sendRemotePacket(CrossDevicePackets.REQUEST_DISCONNECT.packet) // CrossDevice.sendRemotePacket(CrossDevicePackets.REQUEST_DISCONNECT.packet)
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 { device = getSystemService(BluetoothManager::class.java).adapter.bondedDevices.find {
it.address == macAddress it.address == macAddress
} }
@@ -2340,7 +2372,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
showIsland(this, batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level!!.coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level!!), showIsland(this, batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level!!.coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level!!),
IslandType.TAKING_OVER) IslandType.TAKING_OVER)
CrossDevice.isAvailable = false // CrossDevice.isAvailable = false
} }
private fun createBluetoothSocket(device: BluetoothDevice, uuid: ParcelUuid): BluetoothSocket { private fun createBluetoothSocket(device: BluetoothDevice, uuid: ParcelUuid): BluetoothSocket {
@@ -2385,7 +2417,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
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 && !CrossDevice.isAvailable) { if (!isConnectedLocally) {
socket = try { socket = try {
createBluetoothSocket(device, uuid) createBluetoothSocket(device, uuid)
} catch (e: Exception) { } catch (e: Exception) {
@@ -2503,7 +2535,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
}) })
val bytes = buffer.copyOfRange(0, bytesRead) val bytes = buffer.copyOfRange(0, bytesRead)
val formattedHex = bytes.joinToString(" ") { "%02X".format(it) } val formattedHex = bytes.joinToString(" ") { "%02X".format(it) }
CrossDevice.sendReceivedPacket(bytes) // CrossDevice.sendReceivedPacket(bytes)
updateNotificationContent( updateNotificationContent(
true, true,
sharedPreferences.getString("name", device.name), sharedPreferences.getString("name", device.name),
@@ -2541,6 +2573,8 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
this@AirPodsService.device = device this@AirPodsService.device = device
updateNotificationContent(false) updateNotificationContent(false)
} }
} else {
Log.d(TAG, "Already connected locally, skipping socket connection (isConnectedLocally = $isConnectedLocally, socket.isConnected = ${this::socket.isInitialized && socket.isConnected})")
} }
} }
@@ -2566,7 +2600,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
override fun onServiceDisconnected(profile: Int) {} override fun onServiceDisconnected(profile: Int) {}
}, BluetoothProfile.A2DP) }, BluetoothProfile.A2DP)
isConnectedLocally = false isConnectedLocally = false
CrossDevice.isAvailable = true // CrossDevice.isAvailable = true
} }
fun disconnectAirPods() { fun disconnectAirPods() {
@@ -2611,16 +2645,16 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
} }
fun getBattery(): List<Battery> { fun getBattery(): List<Battery> {
if (!isConnectedLocally && CrossDevice.isAvailable) { // if (!isConnectedLocally && CrossDevice.isAvailable) {
batteryNotification.setBattery(CrossDevice.batteryBytes) // batteryNotification.setBattery(CrossDevice.batteryBytes)
} // }
return batteryNotification.getBattery() return batteryNotification.getBattery()
} }
fun getANC(): Int { fun getANC(): Int {
if (!isConnectedLocally && CrossDevice.isAvailable) { // if (!isConnectedLocally && CrossDevice.isAvailable) {
ancNotification.setStatus(CrossDevice.ancBytes) // ancNotification.setStatus(CrossDevice.ancBytes)
} // }
return ancNotification.status return ancNotification.status
} }
@@ -2761,7 +2795,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
} }
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE) telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE)
isConnectedLocally = false isConnectedLocally = false
CrossDevice.isAvailable = true // CrossDevice.isAvailable = true
super.onDestroy() super.onDestroy()
} }

View File

@@ -596,7 +596,7 @@ class AACPManager {
eqData = FloatArray(8) { i -> eq1.get(i) } eqData = FloatArray(8) { i -> eq1.get(i) }
Log.d(TAG, "EQ Data set to: ${eqData.toList()}, eqOnPhone: $eqOnPhone, eqOnMedia: $eqOnMedia") Log.d(TAG, "EQ Data set to: ${eqData.toList()}, eqOnPhone: $eqOnPhone, eqOnMedia: $eqOnMedia")
} }
Opcodes.INFORMATION -> { Opcodes.INFORMATION -> {
Log.e(TAG, "Parsing Information Packet") Log.e(TAG, "Parsing Information Packet")
val information = parseInformationPacket(packet) val information = parseInformationPacket(packet)
@@ -1201,7 +1201,8 @@ class AACPManager {
var offset = 9 var offset = 9
for (i in 0 until deviceCount) { for (i in 0 until deviceCount) {
if (offset + 8 > data.size) { if (offset + 8 > data.size) {
throw IllegalArgumentException("Data array too short to parse all connected devices") Log.w(TAG, "Data array too short to parse all connected devices, returning what we have")
break
} }
val macBytes = data.sliceArray(offset until offset + 6) val macBytes = data.sliceArray(offset until offset + 6)
val mac = macBytes.joinToString(":") { "%02X".format(it) } val mac = macBytes.joinToString(":") { "%02X".format(it) }

View File

@@ -149,7 +149,8 @@ class AirPodsPro2Lightning: AirPodsBase(
Capability.HEARING_AID, Capability.HEARING_AID,
Capability.ADAPTIVE_AUDIO, Capability.ADAPTIVE_AUDIO,
Capability.ADAPTIVE_VOLUME, Capability.ADAPTIVE_VOLUME,
Capability.SWIPE_FOR_VOLUME Capability.SWIPE_FOR_VOLUME,
Capability.HEAD_GESTURES
) )
) )
@@ -171,7 +172,8 @@ class AirPodsPro2USBC: AirPodsBase(
Capability.HEARING_AID, Capability.HEARING_AID,
Capability.ADAPTIVE_AUDIO, Capability.ADAPTIVE_AUDIO,
Capability.ADAPTIVE_VOLUME, Capability.ADAPTIVE_VOLUME,
Capability.SWIPE_FOR_VOLUME Capability.SWIPE_FOR_VOLUME,
Capability.HEAD_GESTURES
) )
) )
@@ -230,4 +232,4 @@ object AirPodsModels {
fun getModelByModelNumber(modelNumber: String): AirPodsBase? { fun getModelByModelNumber(modelNumber: String): AirPodsBase? {
return models.find { modelNumber in it.modelNumber } return models.find { modelNumber in it.modelNumber }
} }
} }

View File

@@ -115,6 +115,11 @@ class RadareOffsetFinder(context: Context) {
} }
fun isSdpOffsetAvailable(): Boolean { fun isSdpOffsetAvailable(): Boolean {
val sharedPreferences = ServiceManager.getService()?.applicationContext?.getSharedPreferences("settings", Context.MODE_PRIVATE) // ik not good practice- too lazy
if (sharedPreferences?.getBoolean("skip_setup", false) == true) {
Log.d(TAG, "Setup skipped, returning true for SDP offset.")
return true
}
try { try {
val process = Runtime.getRuntime().exec(arrayOf("/system/bin/getprop", SDP_OFFSET_PROP)) val process = Runtime.getRuntime().exec(arrayOf("/system/bin/getprop", SDP_OFFSET_PROP))
val reader = BufferedReader(InputStreamReader(process.inputStream)) val reader = BufferedReader(InputStreamReader(process.inputStream))
@@ -462,7 +467,7 @@ class RadareOffsetFinder(context: Context) {
// findAndSaveL2cuProcessCfgReqOffset(libraryPath, envSetup) // findAndSaveL2cuProcessCfgReqOffset(libraryPath, envSetup)
// findAndSaveL2cCsmConfigOffset(libraryPath, envSetup) // findAndSaveL2cCsmConfigOffset(libraryPath, envSetup)
// findAndSaveL2cuSendPeerInfoReqOffset(libraryPath, envSetup) // findAndSaveL2cuSendPeerInfoReqOffset(libraryPath, envSetup)
// findAndSaveSdpOffset(libraryPath, envSetup) Should not be run by default, only when user asks for it. // findAndSaveSdpOffset(libraryPath, envSetup) Should not be run by default, only when user asks for it.
} catch (e: Exception) { } catch (e: Exception) {