Skip to content

Commit 5bef8c3

Browse files
committed
android: add toggle for DID hook
1 parent 9e6d971 commit 5bef8c3

File tree

2 files changed

+191
-2
lines changed

2 files changed

+191
-2
lines changed

android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ package me.kavishdevar.librepods.screens
2020

2121
import android.content.Context
2222
import android.widget.Toast
23+
import androidx.activity.compose.BackHandler
2324
import androidx.compose.foundation.background
2425
import androidx.compose.foundation.clickable
2526
import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -99,6 +100,9 @@ import me.kavishdevar.librepods.utils.RadareOffsetFinder
99100
import kotlin.io.encoding.Base64
100101
import kotlin.io.encoding.ExperimentalEncodingApi
101102
import kotlin.math.roundToInt
103+
import androidx.compose.runtime.rememberCoroutineScope
104+
import kotlinx.coroutines.CoroutineScope
105+
import kotlinx.coroutines.launch
102106

103107
@OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class, ExperimentalEncodingApi::class)
104108
@Composable
@@ -107,6 +111,7 @@ fun AppSettingsScreen(navController: NavController) {
107111
val name = remember { mutableStateOf(sharedPreferences.getString("name", "") ?: "") }
108112
val isDarkTheme = isSystemInDarkTheme()
109113
val context = LocalContext.current
114+
val coroutineScope = rememberCoroutineScope()
110115
val scrollState = rememberScrollState()
111116
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
112117
val hazeState = remember { HazeState() }
@@ -200,6 +205,11 @@ fun AppSettingsScreen(navController: NavController) {
200205
return hexPattern.matches(input)
201206
}
202207

208+
var isProcessingSdp by remember { mutableStateOf(false) }
209+
var actAsAppleDevice by remember { mutableStateOf(false) }
210+
211+
BackHandler(enabled = isProcessingSdp) {}
212+
203213
Scaffold(
204214
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
205215
topBar = {
@@ -233,8 +243,11 @@ fun AppSettingsScreen(navController: NavController) {
233243
navigationIcon = {
234244
TextButton(
235245
onClick = {
236-
navController.popBackStack()
246+
if (!isProcessingSdp) {
247+
navController.popBackStack()
248+
}
237249
},
250+
enabled = !isProcessingSdp,
238251
shape = RoundedCornerShape(8.dp),
239252
modifier = Modifier.width(180.dp)
240253
) {
@@ -1189,6 +1202,91 @@ fun AppSettingsScreen(navController: NavController) {
11891202
)
11901203
}
11911204
}
1205+
1206+
LaunchedEffect(Unit) {
1207+
actAsAppleDevice = RadareOffsetFinder.isSdpOffsetAvailable()
1208+
}
1209+
1210+
Row(
1211+
modifier = Modifier
1212+
.fillMaxWidth()
1213+
.clickable(
1214+
enabled = !isProcessingSdp,
1215+
indication = null,
1216+
interactionSource = remember { MutableInteractionSource() }
1217+
) {
1218+
if (!isProcessingSdp) {
1219+
val newValue = !actAsAppleDevice
1220+
actAsAppleDevice = newValue
1221+
isProcessingSdp = true
1222+
coroutineScope.launch {
1223+
if (newValue) {
1224+
val radareOffsetFinder = RadareOffsetFinder(context)
1225+
val success = radareOffsetFinder.findSdpOffset() ?: false
1226+
if (success) {
1227+
Toast.makeText(context, "Found offset please restart the Bluetooth process", Toast.LENGTH_LONG).show()
1228+
}
1229+
} else {
1230+
RadareOffsetFinder.clearSdpOffset()
1231+
}
1232+
isProcessingSdp = false
1233+
}
1234+
}
1235+
},
1236+
verticalAlignment = Alignment.CenterVertically
1237+
) {
1238+
Column(
1239+
modifier = Modifier
1240+
.weight(1f)
1241+
.padding(vertical = 8.dp)
1242+
.padding(end = 4.dp)
1243+
) {
1244+
Text(
1245+
text = "Act as an Apple device",
1246+
fontSize = 16.sp,
1247+
color = textColor
1248+
)
1249+
Spacer(modifier = Modifier.height(4.dp))
1250+
Text(
1251+
text = "Enables multi-device connectivity and Accessibility features like customizing transparency mode (amplification, tone, ambient noise reduction, conversation boost, and EQ)",
1252+
fontSize = 14.sp,
1253+
color = textColor.copy(0.6f),
1254+
lineHeight = 16.sp,
1255+
)
1256+
if (actAsAppleDevice) {
1257+
Spacer(modifier = Modifier.height(8.dp))
1258+
Text(
1259+
text = "Might be unstable!! A maximum of two devices can be connected to your AirPods. If you are using with an Apple device like an iPad or Mac, then please connect that device first and then your Android.",
1260+
fontSize = 12.sp,
1261+
color = MaterialTheme.colorScheme.error,
1262+
lineHeight = 14.sp,
1263+
)
1264+
}
1265+
}
1266+
1267+
StyledSwitch(
1268+
checked = actAsAppleDevice,
1269+
onCheckedChange = {
1270+
if (!isProcessingSdp) {
1271+
actAsAppleDevice = it
1272+
isProcessingSdp = true
1273+
coroutineScope.launch {
1274+
if (it) {
1275+
val radareOffsetFinder = RadareOffsetFinder(context)
1276+
val success = radareOffsetFinder.findSdpOffset() ?: false
1277+
if (success) {
1278+
Toast.makeText(context, "Found offset please restart the Bluetooth process", Toast.LENGTH_LONG).show()
1279+
}
1280+
} else {
1281+
RadareOffsetFinder.clearSdpOffset()
1282+
}
1283+
isProcessingSdp = false
1284+
}
1285+
}
1286+
},
1287+
enabled = !isProcessingSdp
1288+
)
1289+
}
11921290
}
11931291

11941292
Spacer(modifier = Modifier.height(16.dp))

android/app/src/main/java/me/kavishdevar/librepods/utils/RadareOffsetFinder.kt

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ class RadareOffsetFinder(context: Context) {
7878
"setprop $HOOK_OFFSET_PROP '' && " +
7979
"setprop $CFG_REQ_OFFSET_PROP '' && " +
8080
"setprop $CSM_CONFIG_OFFSET_PROP '' && " +
81-
"setprop $PEER_INFO_REQ_OFFSET_PROP ''" +
81+
"setprop $PEER_INFO_REQ_OFFSET_PROP '' &&" +
8282
"setprop $SDP_OFFSET_PROP ''"
8383
))
8484
val exitCode = process.waitFor()
@@ -94,6 +94,44 @@ class RadareOffsetFinder(context: Context) {
9494
}
9595
return false
9696
}
97+
98+
fun clearSdpOffset(): Boolean {
99+
try {
100+
val process = Runtime.getRuntime().exec(arrayOf(
101+
"su", "-c", "setprop $SDP_OFFSET_PROP ''"
102+
))
103+
val exitCode = process.waitFor()
104+
105+
if (exitCode == 0) {
106+
Log.d(TAG, "Successfully cleared SDP offset property")
107+
return true
108+
} else {
109+
Log.e(TAG, "Failed to clear SDP offset property, exit code: $exitCode")
110+
}
111+
} catch (e: Exception) {
112+
Log.e(TAG, "Error clearing SDP offset property", e)
113+
}
114+
return false
115+
}
116+
117+
fun isSdpOffsetAvailable(): Boolean {
118+
try {
119+
val process = Runtime.getRuntime().exec(arrayOf("getprop", SDP_OFFSET_PROP))
120+
val reader = BufferedReader(InputStreamReader(process.inputStream))
121+
val propValue = reader.readLine()
122+
process.waitFor()
123+
124+
if (propValue != null && propValue.isNotEmpty()) {
125+
Log.d(TAG, "SDP offset property exists: $propValue")
126+
return true
127+
}
128+
} catch (e: Exception) {
129+
Log.e(TAG, "Error checking if SDP offset property exists", e)
130+
}
131+
132+
Log.d(TAG, "No SDP offset available")
133+
return false
134+
}
97135
}
98136

99137
private val radare2TarballFile = File(context.cacheDir, "radare2.tar.gz")
@@ -661,4 +699,57 @@ class RadareOffsetFinder(context: Context) {
661699
Log.e(TAG, "Failed to cleanup extracted files", e)
662700
}
663701
}
702+
703+
suspend fun findSdpOffset(): Boolean = withContext(Dispatchers.IO) {
704+
try {
705+
_progressState.value = ProgressState.Downloading
706+
if (!downloadRadare2TarballIfNeeded()) {
707+
_progressState.value = ProgressState.Error("Failed to download radare2 tarball")
708+
Log.e(TAG, "Failed to download radare2 tarball")
709+
return@withContext false
710+
}
711+
712+
_progressState.value = ProgressState.Extracting
713+
if (!extractRadare2Tarball()) {
714+
_progressState.value = ProgressState.Error("Failed to extract radare2 tarball")
715+
Log.e(TAG, "Failed to extract radare2 tarball")
716+
return@withContext false
717+
}
718+
719+
_progressState.value = ProgressState.MakingExecutable
720+
if (!makeExecutable()) {
721+
_progressState.value = ProgressState.Error("Failed to make binaries executable")
722+
Log.e(TAG, "Failed to make binaries executable")
723+
return@withContext false
724+
}
725+
726+
_progressState.value = ProgressState.FindingOffset
727+
val libraryPath = findBluetoothLibraryPath()
728+
if (libraryPath == null) {
729+
_progressState.value = ProgressState.Error("Failed to find Bluetooth library")
730+
Log.e(TAG, "Failed to find Bluetooth library")
731+
return@withContext false
732+
}
733+
734+
@Suppress("LocalVariableName") val currentLD_LIBRARY_PATH = ProcessBuilder().command("su", "-c", "printenv LD_LIBRARY_PATH").start().inputStream.bufferedReader().readText().trim()
735+
val currentPATH = ProcessBuilder().command("su", "-c", "printenv PATH").start().inputStream.bufferedReader().readText().trim()
736+
val envSetup = """
737+
export LD_LIBRARY_PATH="$RADARE2_LIB_PATH:$currentLD_LIBRARY_PATH"
738+
export PATH="$BUSYBOX_PATH:$RADARE2_BIN_PATH:$currentPATH"
739+
""".trimIndent()
740+
741+
findAndSaveSdpOffset(libraryPath, envSetup)
742+
743+
_progressState.value = ProgressState.Cleaning
744+
cleanupExtractedFiles()
745+
746+
_progressState.value = ProgressState.Success(0L)
747+
return@withContext true
748+
749+
} catch (e: Exception) {
750+
_progressState.value = ProgressState.Error("Error: ${e.message}")
751+
Log.e(TAG, "Error in findSdpOffset", e)
752+
return@withContext false
753+
}
754+
}
664755
}

0 commit comments

Comments
 (0)