android: fix the xposed module

skip unecessary parsing the argument for debugging, just return true and hope that it works
This commit is contained in:
Kavish Devar
2025-05-08 23:50:30 +05:30
parent 91675de891
commit 58dfed97b3
15 changed files with 406 additions and 53 deletions

View File

@@ -13,8 +13,8 @@ android {
applicationId = "me.kavishdevar.librepods"
minSdk = 28
targetSdk = 35
versionCode = 4
versionName = "0.1.0"
versionCode = 5
versionName = "0.1.0-rc.2"
}
buildTypes {

View File

@@ -1,24 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
xmlns:tools="http://schemas.android.com/tools"
android:sharedUserId="android.uid.system"
android:sharedUserMaxSdkVersion="32"
tools:targetApi="33">
<uses-feature
android:name="android.hardware.telephony"
android:required="false" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission
android:name="android.permission.BLUETOOTH_PRIVILEGED"
tools:ignore="ProtectedPermissions" />
<uses-permission
android:name="android.permission.BATTERY_STATS"
tools:ignore="ProtectedPermissions" />
<uses-permission
android:name="android.permission.UPDATE_DEVICE_STATS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission
android:name="android.permission.BLUETOOTH_SCAN"
@@ -30,6 +32,8 @@
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.INTERNET" />
<protected-broadcast android:name="batterywidget.impl.action.update_bluetooth_data" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"

View File

@@ -32,9 +32,7 @@
static HookFunType hook_func = nullptr;
#define L2CEVT_L2CAP_CONFIG_REQ 4
#define L2CEVT_L2CAP_CONFIG_RSP 15
// Define all necessary structures for the L2CAP stack
// Forward declarations for types needed by the new hook
struct t_l2c_lcb;
typedef struct _BT_HDR {
uint16_t event;
@@ -44,7 +42,6 @@ typedef struct _BT_HDR {
uint8_t data[];
} BT_HDR;
// Define base FCR structures
typedef struct {
uint8_t mode;
uint8_t tx_win_sz;
@@ -130,17 +127,7 @@ static void (*original_l2c_csm_config)(tL2C_CCB* p_ccb, uint8_t event, void* p_d
static void (*original_l2cu_send_peer_info_req)(tL2C_LCB* p_lcb, uint16_t info_type) = nullptr;
uint8_t fake_l2c_fcr_chk_chan_modes(void* p_ccb) {
LOGI("l2c_fcr_chk_chan_modes hooked");
auto* ccb = static_cast<tL2C_CCB*>(p_ccb);
LOGI("Original FCR mode: 0x%02x", ccb->our_cfg.fcr.mode);
ccb->our_cfg.fcr.mode = 0;
ccb->our_cfg.fcr_present = true;
ccb->peer_cfg.fcr.mode = 0;
ccb->peer_cfg.fcr_present = true;
LOGI("FCR mode set to Basic Mode (0) for both local and peer config, here's the new desired FCR mode: 0x%02x, and the peer's FCR mode: 0x%02x", ccb->our_cfg.fcr.mode, ccb->peer_cfg.fcr.mode);
LOGI("l2c_fcr_chk_chan_modes hooked, returning true.");
return 1;
}

View File

@@ -186,6 +186,8 @@ fun Main() {
permissions = listOf(
"android.permission.BLUETOOTH_CONNECT",
"android.permission.BLUETOOTH_SCAN",
"android.permission.BLUETOOTH",
"android.permission.BLUETOOTH_ADMIN",
"android.permission.BLUETOOTH_ADVERTISE",
"android.permission.POST_NOTIFICATIONS",
"android.permission.READ_PHONE_STATE",
@@ -517,16 +519,16 @@ fun PermissionsScreen(
),
)
}
if (!canDrawOverlays && basicPermissionsGranted) {
Spacer(modifier = Modifier.height(12.dp))
Button(
onClick = {
val editor = context.getSharedPreferences("settings", MODE_PRIVATE).edit()
editor.putBoolean("overlay_permission_skipped", true)
editor.apply()
val intent = Intent(context, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
context.startActivity(intent)

View File

@@ -71,6 +71,7 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
@@ -106,6 +107,7 @@ fun AppSettingsScreen(navController: NavController) {
navController.popBackStack()
},
shape = RoundedCornerShape(8.dp),
modifier = Modifier.width(180.dp)
) {
Icon(
Icons.AutoMirrored.Filled.KeyboardArrowLeft,
@@ -121,6 +123,9 @@ fun AppSettingsScreen(navController: NavController) {
color = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5),
fontFamily = FontFamily(Font(R.font.sf_pro))
),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f)
)
}
},
@@ -142,10 +147,22 @@ fun AppSettingsScreen(navController: NavController) {
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)
val textColor = if (isDarkTheme) Color.White else Color.Black
val accentColor = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5)
IndependentToggle("Show phone battery in widget", ServiceManager.getService()!!, "setPhoneBatteryInWidget", sharedPreferences)
Text(
text = stringResource(R.string.conversational_awareness_customization).uppercase(),
style = TextStyle(
fontSize = 14.sp,
fontWeight = FontWeight.Light,
color = (if (isSystemInDarkTheme()) Color.White else Color.Black).copy(alpha = 0.6f),
fontFamily = FontFamily(Font(R.font.sf_pro))
),
modifier = Modifier.padding(8.dp, bottom = 2.dp)
)
Spacer(modifier = Modifier.height(2.dp))
Column (
modifier = Modifier
.fillMaxWidth()
@@ -170,17 +187,6 @@ fun AppSettingsScreen(navController: NavController) {
val thumbColor = if (isDarkTheme) Color(0xFFFFFFFF) else Color(0xFFFFFFFF)
val labelTextColor = if (isDarkTheme) Color.White else Color.Black
Text(
text = stringResource(R.string.conversational_awareness_customization),
style = TextStyle(
fontSize = 20.sp,
color = textColor
),
modifier = Modifier
.padding(top = 12.dp, bottom = 4.dp)
)
var conversationalAwarenessPauseMusicEnabled by remember {
mutableStateOf(
sharedPreferences.getBoolean("conversational_awareness_pause_music", true)
@@ -367,6 +373,70 @@ fun AppSettingsScreen(navController: NavController) {
Spacer(modifier = Modifier.height(24.dp))
Column(
modifier = Modifier
.fillMaxWidth()
.background(
backgroundColor,
RoundedCornerShape(14.dp)
)
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
var openDialogForControlling by remember {
mutableStateOf(
sharedPreferences.getString("qs_click_behavior", "dialog") == "dialog"
)
}
fun updateQsClickBehavior(enabled: Boolean) {
openDialogForControlling = enabled
sharedPreferences.edit().putString("qs_click_behavior", if (enabled) "dialog" else "cycle").apply()
}
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
updateQsClickBehavior(!openDialogForControlling)
},
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier
.weight(1f)
.padding(vertical = 16.dp)
.padding(end = 4.dp)
) {
Text(
text = "Open dialog for controlling",
fontSize = 16.sp,
color = textColor
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = if (openDialogForControlling)
"If disabled, clicking on the QS will cycle through modes"
else "If enabled, it will show a dialog for controlling noise control mode and conversational awareness",
fontSize = 14.sp,
color = textColor.copy(0.6f),
lineHeight = 16.sp,
)
}
StyledSwitch(
checked = openDialogForControlling,
onCheckedChange = {
updateQsClickBehavior(it)
}
)
}
}
Spacer(modifier = Modifier.height(24.dp))
Button(
onClick = { showResetDialog = true },
modifier = Modifier

View File

@@ -39,6 +39,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
@@ -92,6 +93,7 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -138,6 +140,7 @@ fun HeadTrackingScreen(navController: NavController) {
if (ServiceManager.getService()?.isHeadTrackingActive == true) ServiceManager.getService()?.stopHeadTracking()
},
shape = RoundedCornerShape(8.dp),
modifier = Modifier.width(180.dp)
) {
Icon(
Icons.AutoMirrored.Filled.KeyboardArrowLeft,
@@ -153,6 +156,9 @@ fun HeadTrackingScreen(navController: NavController) {
color = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5),
fontFamily = FontFamily(Font(R.font.sf_pro))
),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f)
)
}
},

View File

@@ -31,6 +31,8 @@ import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.compose.material3.ExperimentalMaterial3Api
import me.kavishdevar.librepods.MainActivity
import me.kavishdevar.librepods.QuickSettingsDialogActivity
import me.kavishdevar.librepods.R
import me.kavishdevar.librepods.utils.AirPodsNotifications
@@ -260,4 +262,42 @@ class AirPodsQSService : TileService() {
else -> R.drawable.airpods
}
}
@ExperimentalMaterial3Api
override fun onTileAdded() {
super.onTileAdded()
Log.d("AirPodsQSService", "Tile added")
val intent = Intent(this, MainActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
}
@ExperimentalMaterial3Api
fun openMainActivity() {
Log.d("AirPodsQSService", "Opening MainActivity")
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
val pendingIntent = PendingIntent.getActivity(
this,
0,
Intent(this, MainActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
},
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
startActivityAndCollapse(pendingIntent)
} else {
@Suppress("DEPRECATION")
val intent = Intent(this, MainActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
startActivityAndCollapse(intent)
}
Log.d("AirPodsQSService", "Called startActivityAndCollapse for MainActivity")
} catch (e: Exception) {
Log.e("AirPodsQSService", "Error launching MainActivity: $e")
}
}
}

View File

@@ -26,12 +26,14 @@ import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.appwidget.AppWidgetManager
import android.bluetooth.BluetoothAssignedNumbers.APPLE
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothProfile
import android.bluetooth.BluetoothSocket
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
@@ -39,6 +41,7 @@ import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.content.res.Resources
import android.media.AudioManager
import android.net.Uri
import android.os.BatteryManager
import android.os.Binder
import android.os.Build
@@ -46,6 +49,7 @@ import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.os.ParcelUuid
import android.os.UserHandle
import android.provider.Settings
import android.telecom.TelecomManager
import android.telephony.PhoneStateListener
@@ -85,6 +89,26 @@ import me.kavishdevar.librepods.utils.LongPressPackets
import me.kavishdevar.librepods.utils.MediaController
import me.kavishdevar.librepods.utils.PopupWindow
import me.kavishdevar.librepods.utils.RadareOffsetFinder
import me.kavishdevar.librepods.utils.SystemApisUtils
import me.kavishdevar.librepods.utils.SystemApisUtils.DEVICE_TYPE_UNTETHERED_HEADSET
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_COMPANION_APP
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_DEVICE_TYPE
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_MAIN_BATTERY
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_MAIN_ICON
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_MANUFACTURER_NAME
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_MODEL_NAME
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_UNTETHERED_CASE_BATTERY
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_UNTETHERED_CASE_CHARGING
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_UNTETHERED_CASE_ICON
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_UNTETHERED_LEFT_BATTERY
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_UNTETHERED_LEFT_CHARGING
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_UNTETHERED_LEFT_ICON
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_UNTETHERED_RIGHT_BATTERY
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_UNTETHERED_RIGHT_CHARGING
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_UNTETHERED_RIGHT_ICON
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD
import me.kavishdevar.librepods.utils.isHeadTrackingData
import me.kavishdevar.librepods.widgets.BatteryWidget
import me.kavishdevar.librepods.widgets.NoiseControlWidget
@@ -295,7 +319,7 @@ class AirPodsService : Service() {
object BatteryChangedIntentReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
if (intent.action == Intent.ACTION_BATTERY_CHANGED) {
ServiceManager.getService()?.updateBatteryWidget()
ServiceManager.getService()?.updateBattery()
} else if (intent.action == AirPodsNotifications.DISCONNECT_RECEIVERS) {
try {
context?.unregisterReceiver(this)
@@ -308,7 +332,7 @@ class AirPodsService : Service() {
fun setPhoneBatteryInWidget(enabled: Boolean) {
widgetMobileBatteryEnabled = enabled
updateBatteryWidget()
updateBattery()
}
@OptIn(ExperimentalMaterial3Api::class)
@@ -369,7 +393,8 @@ class AirPodsService : Service() {
}
@OptIn(ExperimentalMaterial3Api::class)
fun updateBatteryWidget() {
fun updateBattery() {
// Update widget
val appWidgetManager = AppWidgetManager.getInstance(this)
val componentName = ComponentName(this, BatteryWidget::class.java)
val widgetIds = appWidgetManager.getAppWidgetIds(componentName)
@@ -463,6 +488,43 @@ class AirPodsService : Service() {
}
}
appWidgetManager.updateAppWidget(widgetIds, remoteViews)
// set metadata
device?.let {
SystemApisUtils.setMetadata(
it,
it.METADATA_UNTETHERED_CASE_BATTERY,
batteryNotification.getBattery().find { it.component == BatteryComponent.CASE }?.level.toString().toByteArray()
)
SystemApisUtils.setMetadata(
it,
it.METADATA_UNTETHERED_CASE_CHARGING,
(if (batteryNotification.getBattery().find { it.component == BatteryComponent.CASE}?.status == BatteryStatus.CHARGING) "1".toByteArray() else "0".toByteArray())
)
SystemApisUtils.setMetadata(
it,
it.METADATA_UNTETHERED_LEFT_BATTERY,
batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT }?.level.toString().toByteArray()
)
SystemApisUtils.setMetadata(
it,
it.METADATA_UNTETHERED_LEFT_CHARGING,
(if (batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.status == BatteryStatus.CHARGING) "1".toByteArray() else "0".toByteArray())
)
SystemApisUtils.setMetadata(
it,
it.METADATA_UNTETHERED_RIGHT_BATTERY,
batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT }?.level.toString().toByteArray()
)
SystemApisUtils.setMetadata(
it,
it.METADATA_UNTETHERED_RIGHT_CHARGING,
(if (batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.status == BatteryStatus.CHARGING) "1".toByteArray() else "0".toByteArray())
)
}
// broadcast
// broadcastBatteryInformation()
}
fun updateNoiseControlWidget() {
@@ -678,6 +740,168 @@ class AirPodsService : Service() {
private lateinit var connectionReceiver: BroadcastReceiver
private lateinit var disconnectionReceiver: BroadcastReceiver
private fun resToUri(resId: Int): Uri? {
return try {
Uri.Builder()
.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
.authority("me.kavishdevar.librepods")
.appendPath(applicationContext.resources.getResourceTypeName(resId))
.appendPath(applicationContext.resources.getResourceEntryName(resId))
.build()
} catch (e: Resources.NotFoundException) {
null
}
}
private val VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV = "+IPHONEACCEV"
private val VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL = 1
private val APPLE = 0x004C
private val ACTION_BATTERY_LEVEL_CHANGED = "android.bluetooth.device.action.BATTERY_LEVEL_CHANGED"
private val EXTRA_BATTERY_LEVEL = "android.bluetooth.device.extra.BATTERY_LEVEL"
private val PACKAGE_ASI = "com.google.android.settings.intelligence"
private val ACTION_ASI_UPDATE_BLUETOOTH_DATA = "batterywidget.impl.action.update_bluetooth_data"
@Suppress("MissingPermission")
fun broadcastBatteryInformation() {
if (device == null) return
val batteryList = batteryNotification.getBattery()
val leftBattery = batteryList.find { it.component == BatteryComponent.LEFT }
val rightBattery = batteryList.find { it.component == BatteryComponent.RIGHT }
// Calculate unified battery level (minimum of left and right)
val batteryUnified = minOf(
leftBattery?.level ?: 100,
rightBattery?.level ?: 100
)
// Check charging status
val isLeftCharging = leftBattery?.status == BatteryStatus.CHARGING
val isRightCharging = rightBattery?.status == BatteryStatus.CHARGING
val isChargingMain = isLeftCharging && isRightCharging
// Create arguments for vendor-specific event
val arguments = arrayOf<Any>(
1, // Number of key/value pairs
VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, // IndicatorType: Battery Level
batteryUnified // Battery Level
)
// Broadcast vendor-specific event
val intent = Intent(android.bluetooth.BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT).apply {
putExtra(android.bluetooth.BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD, VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV)
putExtra(android.bluetooth.BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE, android.bluetooth.BluetoothHeadset.AT_CMD_TYPE_SET)
putExtra(android.bluetooth.BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS, arguments)
putExtra(BluetoothDevice.EXTRA_DEVICE, device)
putExtra(BluetoothDevice.EXTRA_NAME, device?.name)
addCategory("${android.bluetooth.BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.$APPLE")
}
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
sendBroadcastAsUser(
intent,
UserHandle.getUserHandleForUid(-1),
Manifest.permission.BLUETOOTH_CONNECT
)
} else {
sendBroadcastAsUser(intent, UserHandle.getUserHandleForUid(-1))
}
} catch (e: Exception) {
Log.e("AirPodsService", "Failed to send vendor-specific event: ${e.message}")
}
// Broadcast battery level changes
val batteryIntent = Intent(ACTION_BATTERY_LEVEL_CHANGED).apply {
putExtra(BluetoothDevice.EXTRA_DEVICE, device)
putExtra(EXTRA_BATTERY_LEVEL, batteryUnified)
}
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
sendBroadcast(batteryIntent, Manifest.permission.BLUETOOTH_CONNECT)
} else {
sendBroadcastAsUser(batteryIntent, UserHandle.getUserHandleForUid(-1))
}
} catch (e: Exception) {
Log.e("AirPodsService", "Failed to send battery level broadcast: ${e.message}")
}
// Update Android Settings Intelligence's battery widget
val statusIntent = Intent(ACTION_ASI_UPDATE_BLUETOOTH_DATA).apply {
setPackage(PACKAGE_ASI)
putExtra(ACTION_BATTERY_LEVEL_CHANGED, intent)
}
try {
sendBroadcastAsUser(statusIntent, UserHandle.getUserHandleForUid(-1))
} catch (e: Exception) {
Log.e("AirPodsService", "Failed to send ASI battery level broadcast: ${e.message}")
}
Log.d("AirPodsService", "Broadcast battery level $batteryUnified% to system")
}
private fun setMetadatas(d: BluetoothDevice) {
d.let{ device ->
val metadataSet = SystemApisUtils.setMetadata(
device,
device.METADATA_MAIN_ICON,
resToUri(R.drawable.pro_2).toString().toByteArray()
) &&
SystemApisUtils.setMetadata(
device,
device.METADATA_MODEL_NAME,
"AirPods Pro (2 Gen.)".toByteArray()
) &&
SystemApisUtils.setMetadata(
device,
device.METADATA_DEVICE_TYPE,
device.DEVICE_TYPE_UNTETHERED_HEADSET.toByteArray()
) &&
SystemApisUtils.setMetadata(
device,
device.METADATA_UNTETHERED_CASE_ICON,
resToUri(R.drawable.pro_2_case).toString().toByteArray()
) &&
SystemApisUtils.setMetadata(
device,
device.METADATA_UNTETHERED_RIGHT_ICON,
resToUri(R.drawable.pro_2_right).toString().toByteArray()
) &&
SystemApisUtils.setMetadata(
device,
device.METADATA_UNTETHERED_LEFT_ICON,
resToUri(R.drawable.pro_2_left).toString().toByteArray()
) &&
SystemApisUtils.setMetadata(
device,
device.METADATA_MANUFACTURER_NAME,
"Apple".toByteArray()
) &&
SystemApisUtils.setMetadata(
device,
device.METADATA_COMPANION_APP,
"me.kavisdevar.librepods".toByteArray()
) &&
SystemApisUtils.setMetadata(
device,
device.METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD,
"20".toByteArray()
) &&
SystemApisUtils.setMetadata(
device,
device.METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD,
"20".toByteArray()
) &&
SystemApisUtils.setMetadata(
device,
device.METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD,
"20".toByteArray()
)
Log.d("AirPodsService", "Metadata set: $metadataSet")
}
}
@SuppressLint("InlinedApi", "MissingPermission", "UnspecifiedRegisterReceiverFlag")
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d("AirPodsService", "Service started")
@@ -778,6 +1002,8 @@ class AirPodsService : Service() {
Log.d("AirPodsService", "$name connected")
showPopup(this@AirPodsService, name.toString())
connectToSocket(device!!)
Log.d("AirPodsService", "Setting metadata")
setMetadatas(device!!)
isConnectedLocally = true
macAddress = device!!.address
sharedPreferences.edit {
@@ -850,6 +1076,7 @@ class AirPodsService : Service() {
if (connectedDevices.isNotEmpty()) {
if (!CrossDevice.isAvailable) {
connectToSocket(device)
setMetadatas(device)
macAddress = device.address
sharedPreferences.edit {
putString("mac_address", macAddress)
@@ -1186,7 +1413,7 @@ class AirPodsService : Service() {
ArrayList(batteryNotification.getBattery())
)
})
updateBatteryWidget()
updateBattery()
updateNotificationContent(
true,
this@AirPodsService.getSharedPreferences(

View File

@@ -238,7 +238,7 @@ object CrossDevice {
batteryBytes = trimmedPacket
ServiceManager.getService()?.batteryNotification?.setBattery(trimmedPacket)
Log.d("CrossDevice", "Battery data: ${ServiceManager.getService()?.batteryNotification?.getBattery()[0]?.level}")
ServiceManager.getService()?.updateBatteryWidget()
ServiceManager.getService()?.updateBattery()
ServiceManager.getService()?.sendBatteryBroadcast()
ServiceManager.getService()?.sendBatteryNotification()
} else if (ServiceManager.getService()?.ancNotification?.isANCData(trimmedPacket) == true) {

View File

@@ -416,9 +416,9 @@ class RadareOffsetFinder(context: Context) {
Log.e(TAG, "rabin2 command failed with exit code $exitCode")
}
// findAndSaveL2cuProcessCfgReqOffset(libraryPath, envSetup)
// findAndSaveL2cCsmConfigOffset(libraryPath, envSetup)
findAndSaveL2cuSendPeerInfoReqOffset(libraryPath, envSetup)
// findAndSaveL2cuProcessCfgReqOffset(libraryPath, envSetup)
// findAndSaveL2cCsmConfigOffset(libraryPath, envSetup)
// findAndSaveL2cuSendPeerInfoReqOffset(libraryPath, envSetup)
} catch (e: Exception) {
Log.e(TAG, "Failed to find function offset", e)

View File

@@ -1,6 +1,8 @@
package me.kavishdevar.librepods.utils
import android.bluetooth.BluetoothDevice
import android.util.Log
import org.lsposed.hiddenapibypass.HiddenApiBypass
object SystemApisUtils {
@@ -282,4 +284,23 @@ object SystemApisUtils {
const val ACTION_BLUETOOTH_HANDSFREE_BATTERY_CHANGED = "android.intent.action.BLUETOOTH_HANDSFREE_BATTERY_CHANGED"
const val EXTRA_SHOW_BT_HANDSFREE_BATTERY = "android.intent.extra.show_bluetooth_handsfree_battery"
const val EXTRA_BT_HANDSFREE_BATTERY_LEVEL = "android.intent.extra.bluetooth_handsfree_battery_level"
/**
* Helper method to set metadata using HiddenApiBypass
*/
fun setMetadata(device: BluetoothDevice, key: Int, value: ByteArray): Boolean {
return try {
val result = HiddenApiBypass.invoke(
BluetoothDevice::class.java,
device,
"setMetadata",
key,
value
) as Boolean
result
} catch (e: Exception) {
Log.e("SystemApisUtils", "Failed to set metadata for key $key", e)
false
}
}
}

View File

@@ -19,15 +19,9 @@
package me.kavishdevar.librepods.widgets
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.content.Intent
import android.widget.RemoteViews
import androidx.compose.material3.ExperimentalMaterial3Api
import me.kavishdevar.librepods.MainActivity
import me.kavishdevar.librepods.R
import me.kavishdevar.librepods.services.ServiceManager
class BatteryWidget : AppWidgetProvider() {
@@ -36,6 +30,6 @@ class BatteryWidget : AppWidgetProvider() {
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
ServiceManager.getService()?.updateBatteryWidget()
ServiceManager.getService()?.updateBattery()
}
}

View File

@@ -24,6 +24,7 @@ import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.content.Intent
import android.util.Log
import android.widget.RemoteViews
import me.kavishdevar.librepods.R
import me.kavishdevar.librepods.services.ServiceManager
@@ -77,6 +78,7 @@ class NoiseControlWidget : AppWidgetProvider() {
super.onReceive(context, intent)
if (intent.action == "ACTION_SET_ANC_MODE") {
val mode = intent.getIntExtra("ANC_MODE", 1)
Log.d("NoiseControlWidget", "Setting ANC mode to $mode")
ServiceManager.getService()?.setANCMode(mode)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB