mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-04-26 16:17:49 +00:00
android: improve debugging
This commit is contained in:
@@ -21,12 +21,18 @@
|
|||||||
package me.kavishdevar.aln.screens
|
package me.kavishdevar.aln.screens
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.ClipData
|
||||||
|
import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -43,10 +49,14 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
|
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
|
||||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||||
|
import androidx.compose.material.icons.filled.Delete
|
||||||
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
import androidx.compose.material.icons.filled.Send
|
import androidx.compose.material.icons.filled.Send
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -64,11 +74,13 @@ import androidx.compose.runtime.derivedStateOf
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.scale
|
import androidx.compose.ui.draw.scale
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.Font
|
import androidx.compose.ui.text.font.Font
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
@@ -83,10 +95,27 @@ import dev.chrisbanes.haze.hazeChild
|
|||||||
import dev.chrisbanes.haze.materials.CupertinoMaterials
|
import dev.chrisbanes.haze.materials.CupertinoMaterials
|
||||||
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import me.kavishdevar.aln.R
|
import me.kavishdevar.aln.R
|
||||||
import me.kavishdevar.aln.services.ServiceManager
|
import me.kavishdevar.aln.services.ServiceManager
|
||||||
import me.kavishdevar.aln.utils.BatteryStatus
|
import me.kavishdevar.aln.utils.BatteryStatus
|
||||||
import me.kavishdevar.aln.utils.isHeadTrackingData
|
import me.kavishdevar.aln.utils.isHeadTrackingData
|
||||||
|
import me.kavishdevar.aln.composables.StyledSwitch
|
||||||
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
|
import androidx.compose.ui.layout.positionInRoot
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
|
import androidx.compose.foundation.gestures.detectDragGestures
|
||||||
|
import androidx.compose.material.icons.filled.Check
|
||||||
|
import androidx.compose.ui.input.pointer.PointerInputChange
|
||||||
|
|
||||||
data class PacketInfo(
|
data class PacketInfo(
|
||||||
val type: String,
|
val type: String,
|
||||||
@@ -286,8 +315,31 @@ fun parseOutgoingPacket(bytes: ByteArray, rawData: String): PacketInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun IOSCheckbox(
|
||||||
|
checked: Boolean,
|
||||||
|
onCheckedChange: (Boolean) -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.size(24.dp)
|
||||||
|
.clickable { onCheckedChange(!checked) },
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
if (checked) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Check,
|
||||||
|
contentDescription = "Checked",
|
||||||
|
tint = if (isSystemInDarkTheme()) Color(0xFF007AFF) else Color(0xFF3C6DF5),
|
||||||
|
modifier = Modifier.size(20.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.Q)
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class, ExperimentalFoundationApi::class)
|
||||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter", "UnspecifiedRegisterReceiverFlag")
|
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter", "UnspecifiedRegisterReceiverFlag")
|
||||||
@Composable
|
@Composable
|
||||||
fun DebugScreen(navController: NavController) {
|
fun DebugScreen(navController: NavController) {
|
||||||
@@ -295,13 +347,37 @@ fun DebugScreen(navController: NavController) {
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
val scrollOffset by remember { derivedStateOf { listState.firstVisibleItemScrollOffset } }
|
val scrollOffset by remember { derivedStateOf { listState.firstVisibleItemScrollOffset } }
|
||||||
val packetLogsFlow = remember { MutableStateFlow(emptySet<String>()) }
|
val focusManager = LocalFocusManager.current
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val showMenu = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val airPodsService = remember { ServiceManager.getService() }
|
||||||
|
val packetLogs = airPodsService?.packetLogsFlow?.collectAsState(emptySet())?.value ?: emptySet()
|
||||||
|
val shouldScrollToBottom = remember { mutableStateOf(true) }
|
||||||
|
|
||||||
|
val refreshTrigger = remember { mutableStateOf(0) }
|
||||||
|
LaunchedEffect(refreshTrigger.value) {
|
||||||
|
while(true) {
|
||||||
|
delay(1000)
|
||||||
|
refreshTrigger.value = refreshTrigger.value + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val expandedItems = remember { mutableStateOf(setOf<Int>()) }
|
val expandedItems = remember { mutableStateOf(setOf<Int>()) }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
fun copyToClipboard(text: String) {
|
||||||
ServiceManager.getService()?.packetLogsFlow?.collect { packetLogsFlow.value = it }
|
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
|
val clip = ClipData.newPlainText("Packet Data", text)
|
||||||
|
clipboard.setPrimaryClip(clip)
|
||||||
|
Toast.makeText(context, "Packet copied to clipboard", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(packetLogs.size, refreshTrigger.value) {
|
||||||
|
if (shouldScrollToBottom.value && packetLogs.isNotEmpty()) {
|
||||||
|
listState.animateScrollToItem(packetLogs.size - 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val packetLogs = packetLogsFlow.collectAsState(setOf()).value
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@@ -309,9 +385,7 @@ fun DebugScreen(navController: NavController) {
|
|||||||
title = { Text("Debug") },
|
title = { Text("Debug") },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = { navController.popBackStack() },
|
||||||
navController.popBackStack()
|
|
||||||
},
|
|
||||||
shape = RoundedCornerShape(8.dp),
|
shape = RoundedCornerShape(8.dp),
|
||||||
) {
|
) {
|
||||||
val sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
val sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
@@ -332,31 +406,105 @@ fun DebugScreen(navController: NavController) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
actions = {
|
||||||
|
Box {
|
||||||
|
IconButton(onClick = { showMenu.value = true }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.MoreVert,
|
||||||
|
contentDescription = "More Options",
|
||||||
|
tint = if (isSystemInDarkTheme()) Color.White else Color.Black
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = showMenu.value,
|
||||||
|
onDismissRequest = { showMenu.value = false },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.hazeChild(
|
.width(250.dp)
|
||||||
|
.background(
|
||||||
|
if (isSystemInDarkTheme()) Color(0xFF1C1B20) else Color(0xFFF2F2F7)
|
||||||
|
)
|
||||||
|
.padding(vertical = 4.dp)
|
||||||
|
) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Auto-scroll",
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Normal
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
IOSCheckbox(
|
||||||
|
checked = shouldScrollToBottom.value,
|
||||||
|
onCheckedChange = { shouldScrollToBottom.value = it }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
shouldScrollToBottom.value = !shouldScrollToBottom.value
|
||||||
|
showMenu.value = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
HorizontalDivider(
|
||||||
|
color = if (isSystemInDarkTheme()) Color(0xFF3A3A3C) else Color(0xFFE5E5EA),
|
||||||
|
thickness = 0.5.dp
|
||||||
|
)
|
||||||
|
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Clear logs",
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Normal
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Delete,
|
||||||
|
contentDescription = "Clear logs",
|
||||||
|
tint = if (isSystemInDarkTheme()) Color(0xFF007AFF) else Color(0xFF3C6DF5)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
ServiceManager.getService()?.clearLogs()
|
||||||
|
expandedItems.value = emptySet()
|
||||||
|
showMenu.value = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.hazeChild(
|
||||||
state = hazeState,
|
state = hazeState,
|
||||||
style = CupertinoMaterials.thick(),
|
style = CupertinoMaterials.thick(),
|
||||||
block = {
|
block = {
|
||||||
alpha = if (scrollOffset > 0) {
|
alpha = if (scrollOffset > 0) 1f else 0f
|
||||||
1f
|
|
||||||
} else {
|
|
||||||
0f
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
|
||||||
containerColor = Color.Transparent
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
containerColor = if (isSystemInDarkTheme()) Color(0xFF000000)
|
containerColor = if (isSystemInDarkTheme()) Color(0xFF000000) else Color(0xFFF2F2F7),
|
||||||
else Color(0xFFF2F2F7),
|
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.haze(hazeState)
|
.haze(hazeState)
|
||||||
.padding(top = paddingValues.calculateTopPadding())
|
.padding(top = paddingValues.calculateTopPadding())
|
||||||
|
.navigationBarsPadding()
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
state = listState,
|
state = listState,
|
||||||
@@ -374,13 +522,18 @@ fun DebugScreen(navController: NavController) {
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 2.dp, horizontal = 4.dp)
|
.padding(vertical = 2.dp, horizontal = 4.dp)
|
||||||
.clickable {
|
.combinedClickable(
|
||||||
|
onClick = {
|
||||||
expandedItems.value = if (isExpanded) {
|
expandedItems.value = if (isExpanded) {
|
||||||
expandedItems.value - index
|
expandedItems.value - index
|
||||||
} else {
|
} else {
|
||||||
expandedItems.value + index
|
expandedItems.value + index
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onLongClick = {
|
||||||
|
copyToClipboard(packetInfo.rawData)
|
||||||
|
}
|
||||||
|
),
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
|
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
|
||||||
shape = RoundedCornerShape(4.dp),
|
shape = RoundedCornerShape(4.dp),
|
||||||
colors = CardDefaults.cardColors(
|
colors = CardDefaults.cardColors(
|
||||||
@@ -476,8 +629,27 @@ fun DebugScreen(navController: NavController) {
|
|||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
|
if (packet.value.text.isNotBlank()) {
|
||||||
airPodsService?.value?.sendPacket(packet.value.text)
|
airPodsService?.value?.sendPacket(packet.value.text)
|
||||||
packet.value = TextFieldValue("")
|
packet.value = TextFieldValue("")
|
||||||
|
focusManager.clearFocus()
|
||||||
|
|
||||||
|
if (shouldScrollToBottom.value && packetLogs.isNotEmpty()) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
try {
|
||||||
|
delay(100)
|
||||||
|
listState.animateScrollToItem(
|
||||||
|
index = (packetLogs.size - 1).coerceAtLeast(0),
|
||||||
|
scrollOffset = 0
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
listState.scrollToItem(
|
||||||
|
index = (packetLogs.size - 1).coerceAtLeast(0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
|
|||||||
@@ -136,30 +136,57 @@ class AirPodsService : Service() {
|
|||||||
|
|
||||||
private lateinit var telephonyManager: TelephonyManager
|
private lateinit var telephonyManager: TelephonyManager
|
||||||
private lateinit var phoneStateListener: PhoneStateListener
|
private lateinit var phoneStateListener: PhoneStateListener
|
||||||
|
private val maxLogEntries = 1000
|
||||||
|
private val inMemoryLogs = mutableSetOf<String>()
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
sharedPreferencesLogs = getSharedPreferences("packet_logs", MODE_PRIVATE)
|
sharedPreferencesLogs = getSharedPreferences("packet_logs", MODE_PRIVATE)
|
||||||
|
|
||||||
|
inMemoryLogs.addAll(sharedPreferencesLogs.getStringSet(packetLogKey, emptySet()) ?: emptySet())
|
||||||
|
_packetLogsFlow.value = inMemoryLogs.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun logPacket(packet: ByteArray, source: String) {
|
private fun logPacket(packet: ByteArray, source: String) {
|
||||||
val packetHex = packet.joinToString(" ") { "%02X".format(it) }
|
val packetHex = packet.joinToString(" ") { "%02X".format(it) }
|
||||||
val logEntry = "$source: $packetHex"
|
val logEntry = "$source: $packetHex"
|
||||||
val logs =
|
|
||||||
sharedPreferencesLogs.getStringSet(packetLogKey, mutableSetOf())?.toMutableSet()
|
synchronized(inMemoryLogs) {
|
||||||
|
inMemoryLogs.add(logEntry)
|
||||||
|
if (inMemoryLogs.size > maxLogEntries) {
|
||||||
|
inMemoryLogs.iterator().next()?.let {
|
||||||
|
inMemoryLogs.remove(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_packetLogsFlow.value = inMemoryLogs.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save to SharedPreferences less frequently - only needed for persistence between sessions
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val logs = sharedPreferencesLogs.getStringSet(packetLogKey, mutableSetOf())?.toMutableSet()
|
||||||
?: mutableSetOf()
|
?: mutableSetOf()
|
||||||
logs.add(logEntry)
|
logs.add(logEntry)
|
||||||
_packetLogsFlow.value = logs
|
// Limit SharedPreferences size
|
||||||
|
if (logs.size > maxLogEntries) {
|
||||||
|
val toKeep = logs.toList().takeLast(maxLogEntries).toSet()
|
||||||
|
sharedPreferencesLogs.edit { putStringSet(packetLogKey, toKeep) }
|
||||||
|
} else {
|
||||||
sharedPreferencesLogs.edit { putStringSet(packetLogKey, logs) }
|
sharedPreferencesLogs.edit { putStringSet(packetLogKey, logs) }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getPacketLogs(): Set<String> {
|
fun getPacketLogs(): Set<String> {
|
||||||
return sharedPreferencesLogs.getStringSet(packetLogKey, emptySet()) ?: emptySet()
|
return inMemoryLogs.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clearPacketLogs() {
|
private fun clearPacketLogs() {
|
||||||
sharedPreferencesLogs.edit { remove(packetLogKey).apply() }
|
synchronized(inMemoryLogs) {
|
||||||
|
inMemoryLogs.clear()
|
||||||
|
_packetLogsFlow.value = emptySet()
|
||||||
|
}
|
||||||
|
sharedPreferencesLogs.edit { remove(packetLogKey) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearLogs() {
|
fun clearLogs() {
|
||||||
@@ -1237,6 +1264,9 @@ class AirPodsService : Service() {
|
|||||||
|
|
||||||
fun sendPacket(packet: String) {
|
fun sendPacket(packet: String) {
|
||||||
val fromHex = packet.split(" ").map { it.toInt(16).toByte() }
|
val fromHex = packet.split(" ").map { it.toInt(16).toByte() }
|
||||||
|
try {
|
||||||
|
logPacket(fromHex.toByteArray(), "Sent")
|
||||||
|
|
||||||
if (!isConnectedLocally && CrossDevice.isAvailable) {
|
if (!isConnectedLocally && CrossDevice.isAvailable) {
|
||||||
CrossDevice.sendRemotePacket(CrossDevicePackets.AIRPODS_DATA_HEADER.packet + fromHex.toByteArray())
|
CrossDevice.sendRemotePacket(CrossDevicePackets.AIRPODS_DATA_HEADER.packet + fromHex.toByteArray())
|
||||||
return
|
return
|
||||||
@@ -1245,11 +1275,19 @@ class AirPodsService : Service() {
|
|||||||
val byteArray = fromHex.toByteArray()
|
val byteArray = fromHex.toByteArray()
|
||||||
socket.outputStream?.write(byteArray)
|
socket.outputStream?.write(byteArray)
|
||||||
socket.outputStream?.flush()
|
socket.outputStream?.flush()
|
||||||
logPacket(byteArray, "Sent")
|
} else {
|
||||||
|
Log.d("AirPodsService", "Cannot send packet: Socket not initialized or connected")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("AirPodsService", "Error sending packet: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendPacket(packet: ByteArray) {
|
fun sendPacket(packet: ByteArray) {
|
||||||
|
try {
|
||||||
|
// Always log the packet
|
||||||
|
logPacket(packet, "Sent")
|
||||||
|
|
||||||
if (!isConnectedLocally && CrossDevice.isAvailable) {
|
if (!isConnectedLocally && CrossDevice.isAvailable) {
|
||||||
CrossDevice.sendRemotePacket(CrossDevicePackets.AIRPODS_DATA_HEADER.packet + packet)
|
CrossDevice.sendRemotePacket(CrossDevicePackets.AIRPODS_DATA_HEADER.packet + packet)
|
||||||
return
|
return
|
||||||
@@ -1257,7 +1295,11 @@ class AirPodsService : Service() {
|
|||||||
if (this::socket.isInitialized && socket.isConnected && socket.outputStream != null) {
|
if (this::socket.isInitialized && socket.isConnected && socket.outputStream != null) {
|
||||||
socket.outputStream?.write(packet)
|
socket.outputStream?.write(packet)
|
||||||
socket.outputStream?.flush()
|
socket.outputStream?.flush()
|
||||||
logPacket(packet, "Sent")
|
} else {
|
||||||
|
Log.d("AirPodsService", "Cannot send packet: Socket not initialized or connected")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("AirPodsService", "Error sending packet: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user