mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-04-11 05:46:10 +00:00
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:
@@ -13,8 +13,8 @@ android {
|
|||||||
applicationId = "me.kavishdevar.librepods"
|
applicationId = "me.kavishdevar.librepods"
|
||||||
minSdk = 28
|
minSdk = 28
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 4
|
versionCode = 5
|
||||||
versionName = "0.1.0"
|
versionName = "0.1.0-rc.2"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|||||||
@@ -1,24 +1,26 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<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.BLUETOOTH_CONNECT" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
|
||||||
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"
|
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"
|
||||||
tools:ignore="ProtectedPermissions" />
|
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.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.BLUETOOTH_PRIVILEGED"
|
android:name="android.permission.BLUETOOTH_PRIVILEGED"
|
||||||
tools:ignore="ProtectedPermissions" />
|
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" />
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.BLUETOOTH_SCAN"
|
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.MODIFY_AUDIO_SETTINGS" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
|
<protected-broadcast android:name="batterywidget.impl.action.update_bluetooth_data" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
|||||||
@@ -32,9 +32,7 @@
|
|||||||
static HookFunType hook_func = nullptr;
|
static HookFunType hook_func = nullptr;
|
||||||
#define L2CEVT_L2CAP_CONFIG_REQ 4
|
#define L2CEVT_L2CAP_CONFIG_REQ 4
|
||||||
#define L2CEVT_L2CAP_CONFIG_RSP 15
|
#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;
|
struct t_l2c_lcb;
|
||||||
typedef struct _BT_HDR {
|
typedef struct _BT_HDR {
|
||||||
uint16_t event;
|
uint16_t event;
|
||||||
@@ -44,7 +42,6 @@ typedef struct _BT_HDR {
|
|||||||
uint8_t data[];
|
uint8_t data[];
|
||||||
} BT_HDR;
|
} BT_HDR;
|
||||||
|
|
||||||
// Define base FCR structures
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t mode;
|
uint8_t mode;
|
||||||
uint8_t tx_win_sz;
|
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;
|
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) {
|
uint8_t fake_l2c_fcr_chk_chan_modes(void* p_ccb) {
|
||||||
LOGI("l2c_fcr_chk_chan_modes hooked");
|
LOGI("l2c_fcr_chk_chan_modes hooked, returning true.");
|
||||||
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);
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -186,6 +186,8 @@ fun Main() {
|
|||||||
permissions = listOf(
|
permissions = listOf(
|
||||||
"android.permission.BLUETOOTH_CONNECT",
|
"android.permission.BLUETOOTH_CONNECT",
|
||||||
"android.permission.BLUETOOTH_SCAN",
|
"android.permission.BLUETOOTH_SCAN",
|
||||||
|
"android.permission.BLUETOOTH",
|
||||||
|
"android.permission.BLUETOOTH_ADMIN",
|
||||||
"android.permission.BLUETOOTH_ADVERTISE",
|
"android.permission.BLUETOOTH_ADVERTISE",
|
||||||
"android.permission.POST_NOTIFICATIONS",
|
"android.permission.POST_NOTIFICATIONS",
|
||||||
"android.permission.READ_PHONE_STATE",
|
"android.permission.READ_PHONE_STATE",
|
||||||
@@ -517,16 +519,16 @@ fun PermissionsScreen(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!canDrawOverlays && basicPermissionsGranted) {
|
if (!canDrawOverlays && basicPermissionsGranted) {
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
val editor = context.getSharedPreferences("settings", MODE_PRIVATE).edit()
|
val editor = context.getSharedPreferences("settings", MODE_PRIVATE).edit()
|
||||||
editor.putBoolean("overlay_permission_skipped", true)
|
editor.putBoolean("overlay_permission_skipped", true)
|
||||||
editor.apply()
|
editor.apply()
|
||||||
|
|
||||||
val intent = Intent(context, MainActivity::class.java)
|
val intent = Intent(context, MainActivity::class.java)
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ 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
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
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.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
@@ -106,6 +107,7 @@ fun AppSettingsScreen(navController: NavController) {
|
|||||||
navController.popBackStack()
|
navController.popBackStack()
|
||||||
},
|
},
|
||||||
shape = RoundedCornerShape(8.dp),
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
modifier = Modifier.width(180.dp)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.AutoMirrored.Filled.KeyboardArrowLeft,
|
Icons.AutoMirrored.Filled.KeyboardArrowLeft,
|
||||||
@@ -121,6 +123,9 @@ fun AppSettingsScreen(navController: NavController) {
|
|||||||
color = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5),
|
color = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5),
|
||||||
fontFamily = FontFamily(Font(R.font.sf_pro))
|
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 backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)
|
||||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
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)
|
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 (
|
Column (
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -170,17 +187,6 @@ fun AppSettingsScreen(navController: NavController) {
|
|||||||
val thumbColor = if (isDarkTheme) Color(0xFFFFFFFF) else Color(0xFFFFFFFF)
|
val thumbColor = if (isDarkTheme) Color(0xFFFFFFFF) else Color(0xFFFFFFFF)
|
||||||
val labelTextColor = if (isDarkTheme) Color.White else Color.Black
|
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 {
|
var conversationalAwarenessPauseMusicEnabled by remember {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
sharedPreferences.getBoolean("conversational_awareness_pause_music", true)
|
sharedPreferences.getBoolean("conversational_awareness_pause_music", true)
|
||||||
@@ -367,6 +373,70 @@ fun AppSettingsScreen(navController: NavController) {
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
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(
|
Button(
|
||||||
onClick = { showResetDialog = true },
|
onClick = { showResetDialog = true },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
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
|
||||||
@@ -92,6 +93,7 @@ import androidx.compose.ui.text.font.FontFamily
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.rememberTextMeasurer
|
import androidx.compose.ui.text.rememberTextMeasurer
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
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.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
|
||||||
@@ -138,6 +140,7 @@ fun HeadTrackingScreen(navController: NavController) {
|
|||||||
if (ServiceManager.getService()?.isHeadTrackingActive == true) ServiceManager.getService()?.stopHeadTracking()
|
if (ServiceManager.getService()?.isHeadTrackingActive == true) ServiceManager.getService()?.stopHeadTracking()
|
||||||
},
|
},
|
||||||
shape = RoundedCornerShape(8.dp),
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
modifier = Modifier.width(180.dp)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.AutoMirrored.Filled.KeyboardArrowLeft,
|
Icons.AutoMirrored.Filled.KeyboardArrowLeft,
|
||||||
@@ -153,6 +156,9 @@ fun HeadTrackingScreen(navController: NavController) {
|
|||||||
color = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5),
|
color = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5),
|
||||||
fontFamily = FontFamily(Font(R.font.sf_pro))
|
fontFamily = FontFamily(Font(R.font.sf_pro))
|
||||||
),
|
),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ import android.service.quicksettings.Tile
|
|||||||
import android.service.quicksettings.TileService
|
import android.service.quicksettings.TileService
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import me.kavishdevar.librepods.MainActivity
|
||||||
import me.kavishdevar.librepods.QuickSettingsDialogActivity
|
import me.kavishdevar.librepods.QuickSettingsDialogActivity
|
||||||
import me.kavishdevar.librepods.R
|
import me.kavishdevar.librepods.R
|
||||||
import me.kavishdevar.librepods.utils.AirPodsNotifications
|
import me.kavishdevar.librepods.utils.AirPodsNotifications
|
||||||
@@ -260,4 +262,42 @@ class AirPodsQSService : TileService() {
|
|||||||
else -> R.drawable.airpods
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,12 +26,14 @@ 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.BluetoothAssignedNumbers.APPLE
|
||||||
import android.bluetooth.BluetoothDevice
|
import android.bluetooth.BluetoothDevice
|
||||||
import android.bluetooth.BluetoothManager
|
import android.bluetooth.BluetoothManager
|
||||||
import android.bluetooth.BluetoothProfile
|
import android.bluetooth.BluetoothProfile
|
||||||
import android.bluetooth.BluetoothSocket
|
import android.bluetooth.BluetoothSocket
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
|
import android.content.ContentResolver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
@@ -39,6 +41,7 @@ import android.content.SharedPreferences
|
|||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
|
import android.net.Uri
|
||||||
import android.os.BatteryManager
|
import android.os.BatteryManager
|
||||||
import android.os.Binder
|
import android.os.Binder
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
@@ -46,6 +49,7 @@ import android.os.Handler
|
|||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.os.ParcelUuid
|
import android.os.ParcelUuid
|
||||||
|
import android.os.UserHandle
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.telecom.TelecomManager
|
import android.telecom.TelecomManager
|
||||||
import android.telephony.PhoneStateListener
|
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.MediaController
|
||||||
import me.kavishdevar.librepods.utils.PopupWindow
|
import me.kavishdevar.librepods.utils.PopupWindow
|
||||||
import me.kavishdevar.librepods.utils.RadareOffsetFinder
|
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.utils.isHeadTrackingData
|
||||||
import me.kavishdevar.librepods.widgets.BatteryWidget
|
import me.kavishdevar.librepods.widgets.BatteryWidget
|
||||||
import me.kavishdevar.librepods.widgets.NoiseControlWidget
|
import me.kavishdevar.librepods.widgets.NoiseControlWidget
|
||||||
@@ -295,7 +319,7 @@ class AirPodsService : Service() {
|
|||||||
object BatteryChangedIntentReceiver : BroadcastReceiver() {
|
object BatteryChangedIntentReceiver : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context?, intent: Intent) {
|
override fun onReceive(context: Context?, intent: Intent) {
|
||||||
if (intent.action == Intent.ACTION_BATTERY_CHANGED) {
|
if (intent.action == Intent.ACTION_BATTERY_CHANGED) {
|
||||||
ServiceManager.getService()?.updateBatteryWidget()
|
ServiceManager.getService()?.updateBattery()
|
||||||
} else if (intent.action == AirPodsNotifications.DISCONNECT_RECEIVERS) {
|
} else if (intent.action == AirPodsNotifications.DISCONNECT_RECEIVERS) {
|
||||||
try {
|
try {
|
||||||
context?.unregisterReceiver(this)
|
context?.unregisterReceiver(this)
|
||||||
@@ -308,7 +332,7 @@ class AirPodsService : Service() {
|
|||||||
|
|
||||||
fun setPhoneBatteryInWidget(enabled: Boolean) {
|
fun setPhoneBatteryInWidget(enabled: Boolean) {
|
||||||
widgetMobileBatteryEnabled = enabled
|
widgetMobileBatteryEnabled = enabled
|
||||||
updateBatteryWidget()
|
updateBattery()
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -369,7 +393,8 @@ class AirPodsService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
fun updateBatteryWidget() {
|
fun updateBattery() {
|
||||||
|
// Update widget
|
||||||
val appWidgetManager = AppWidgetManager.getInstance(this)
|
val appWidgetManager = AppWidgetManager.getInstance(this)
|
||||||
val componentName = ComponentName(this, BatteryWidget::class.java)
|
val componentName = ComponentName(this, BatteryWidget::class.java)
|
||||||
val widgetIds = appWidgetManager.getAppWidgetIds(componentName)
|
val widgetIds = appWidgetManager.getAppWidgetIds(componentName)
|
||||||
@@ -463,6 +488,43 @@ class AirPodsService : Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
appWidgetManager.updateAppWidget(widgetIds, remoteViews)
|
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() {
|
fun updateNoiseControlWidget() {
|
||||||
@@ -678,6 +740,168 @@ class AirPodsService : Service() {
|
|||||||
private lateinit var connectionReceiver: BroadcastReceiver
|
private lateinit var connectionReceiver: BroadcastReceiver
|
||||||
private lateinit var disconnectionReceiver: 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")
|
@SuppressLint("InlinedApi", "MissingPermission", "UnspecifiedRegisterReceiverFlag")
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
Log.d("AirPodsService", "Service started")
|
Log.d("AirPodsService", "Service started")
|
||||||
@@ -778,6 +1002,8 @@ class AirPodsService : Service() {
|
|||||||
Log.d("AirPodsService", "$name connected")
|
Log.d("AirPodsService", "$name connected")
|
||||||
showPopup(this@AirPodsService, name.toString())
|
showPopup(this@AirPodsService, name.toString())
|
||||||
connectToSocket(device!!)
|
connectToSocket(device!!)
|
||||||
|
Log.d("AirPodsService", "Setting metadata")
|
||||||
|
setMetadatas(device!!)
|
||||||
isConnectedLocally = true
|
isConnectedLocally = true
|
||||||
macAddress = device!!.address
|
macAddress = device!!.address
|
||||||
sharedPreferences.edit {
|
sharedPreferences.edit {
|
||||||
@@ -850,6 +1076,7 @@ class AirPodsService : Service() {
|
|||||||
if (connectedDevices.isNotEmpty()) {
|
if (connectedDevices.isNotEmpty()) {
|
||||||
if (!CrossDevice.isAvailable) {
|
if (!CrossDevice.isAvailable) {
|
||||||
connectToSocket(device)
|
connectToSocket(device)
|
||||||
|
setMetadatas(device)
|
||||||
macAddress = device.address
|
macAddress = device.address
|
||||||
sharedPreferences.edit {
|
sharedPreferences.edit {
|
||||||
putString("mac_address", macAddress)
|
putString("mac_address", macAddress)
|
||||||
@@ -1186,7 +1413,7 @@ class AirPodsService : Service() {
|
|||||||
ArrayList(batteryNotification.getBattery())
|
ArrayList(batteryNotification.getBattery())
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
updateBatteryWidget()
|
updateBattery()
|
||||||
updateNotificationContent(
|
updateNotificationContent(
|
||||||
true,
|
true,
|
||||||
this@AirPodsService.getSharedPreferences(
|
this@AirPodsService.getSharedPreferences(
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ object CrossDevice {
|
|||||||
batteryBytes = trimmedPacket
|
batteryBytes = trimmedPacket
|
||||||
ServiceManager.getService()?.batteryNotification?.setBattery(trimmedPacket)
|
ServiceManager.getService()?.batteryNotification?.setBattery(trimmedPacket)
|
||||||
Log.d("CrossDevice", "Battery data: ${ServiceManager.getService()?.batteryNotification?.getBattery()[0]?.level}")
|
Log.d("CrossDevice", "Battery data: ${ServiceManager.getService()?.batteryNotification?.getBattery()[0]?.level}")
|
||||||
ServiceManager.getService()?.updateBatteryWidget()
|
ServiceManager.getService()?.updateBattery()
|
||||||
ServiceManager.getService()?.sendBatteryBroadcast()
|
ServiceManager.getService()?.sendBatteryBroadcast()
|
||||||
ServiceManager.getService()?.sendBatteryNotification()
|
ServiceManager.getService()?.sendBatteryNotification()
|
||||||
} else if (ServiceManager.getService()?.ancNotification?.isANCData(trimmedPacket) == true) {
|
} else if (ServiceManager.getService()?.ancNotification?.isANCData(trimmedPacket) == true) {
|
||||||
|
|||||||
@@ -416,9 +416,9 @@ class RadareOffsetFinder(context: Context) {
|
|||||||
Log.e(TAG, "rabin2 command failed with exit code $exitCode")
|
Log.e(TAG, "rabin2 command failed with exit code $exitCode")
|
||||||
}
|
}
|
||||||
|
|
||||||
// findAndSaveL2cuProcessCfgReqOffset(libraryPath, envSetup)
|
// findAndSaveL2cuProcessCfgReqOffset(libraryPath, envSetup)
|
||||||
// findAndSaveL2cCsmConfigOffset(libraryPath, envSetup)
|
// findAndSaveL2cCsmConfigOffset(libraryPath, envSetup)
|
||||||
findAndSaveL2cuSendPeerInfoReqOffset(libraryPath, envSetup)
|
// findAndSaveL2cuSendPeerInfoReqOffset(libraryPath, envSetup)
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to find function offset", e)
|
Log.e(TAG, "Failed to find function offset", e)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package me.kavishdevar.librepods.utils
|
package me.kavishdevar.librepods.utils
|
||||||
|
|
||||||
import android.bluetooth.BluetoothDevice
|
import android.bluetooth.BluetoothDevice
|
||||||
|
import android.util.Log
|
||||||
|
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
||||||
|
|
||||||
object SystemApisUtils {
|
object SystemApisUtils {
|
||||||
|
|
||||||
@@ -282,4 +284,23 @@ object SystemApisUtils {
|
|||||||
const val ACTION_BLUETOOTH_HANDSFREE_BATTERY_CHANGED = "android.intent.action.BLUETOOTH_HANDSFREE_BATTERY_CHANGED"
|
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_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"
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,15 +19,9 @@
|
|||||||
|
|
||||||
package me.kavishdevar.librepods.widgets
|
package me.kavishdevar.librepods.widgets
|
||||||
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.appwidget.AppWidgetManager
|
import android.appwidget.AppWidgetManager
|
||||||
import android.appwidget.AppWidgetProvider
|
import android.appwidget.AppWidgetProvider
|
||||||
import android.content.Context
|
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
|
import me.kavishdevar.librepods.services.ServiceManager
|
||||||
|
|
||||||
class BatteryWidget : AppWidgetProvider() {
|
class BatteryWidget : AppWidgetProvider() {
|
||||||
@@ -36,6 +30,6 @@ class BatteryWidget : AppWidgetProvider() {
|
|||||||
appWidgetManager: AppWidgetManager,
|
appWidgetManager: AppWidgetManager,
|
||||||
appWidgetIds: IntArray
|
appWidgetIds: IntArray
|
||||||
) {
|
) {
|
||||||
ServiceManager.getService()?.updateBatteryWidget()
|
ServiceManager.getService()?.updateBattery()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import android.appwidget.AppWidgetManager
|
|||||||
import android.appwidget.AppWidgetProvider
|
import android.appwidget.AppWidgetProvider
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import me.kavishdevar.librepods.R
|
import me.kavishdevar.librepods.R
|
||||||
import me.kavishdevar.librepods.services.ServiceManager
|
import me.kavishdevar.librepods.services.ServiceManager
|
||||||
@@ -77,6 +78,7 @@ class NoiseControlWidget : AppWidgetProvider() {
|
|||||||
super.onReceive(context, intent)
|
super.onReceive(context, intent)
|
||||||
if (intent.action == "ACTION_SET_ANC_MODE") {
|
if (intent.action == "ACTION_SET_ANC_MODE") {
|
||||||
val mode = intent.getIntExtra("ANC_MODE", 1)
|
val mode = intent.getIntExtra("ANC_MODE", 1)
|
||||||
|
Log.d("NoiseControlWidget", "Setting ANC mode to $mode")
|
||||||
ServiceManager.getService()?.setANCMode(mode)
|
ServiceManager.getService()?.setANCMode(mode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
android/app/src/main/res/drawable/pro_2_left.png
Normal file
BIN
android/app/src/main/res/drawable/pro_2_left.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
BIN
android/app/src/main/res/drawable/pro_2_right.png
Normal file
BIN
android/app/src/main/res/drawable/pro_2_right.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
Reference in New Issue
Block a user