mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-05-17 14:40:23 +00:00
Compare commits
5 Commits
nightly-d1
...
nightly-21
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
216c97f9ca | ||
|
|
fd3774b513 | ||
|
|
b7336940e6 | ||
|
|
b2ba830a80 | ||
|
|
f08769e62f |
@@ -1,6 +1,6 @@
|
|||||||
import java.util.Properties
|
import java.util.Properties
|
||||||
|
|
||||||
val appVersionName = "0.2.8"
|
val appVersionName = "0.2.9"
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
@@ -30,7 +30,7 @@ android {
|
|||||||
applicationId = "me.kavishdevar.librepods"
|
applicationId = "me.kavishdevar.librepods"
|
||||||
minSdk = 33
|
minSdk = 33
|
||||||
targetSdk = 37
|
targetSdk = 37
|
||||||
versionCode = 49
|
versionCode = 50
|
||||||
versionName = appVersionName
|
versionName = appVersionName
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|||||||
@@ -109,7 +109,8 @@ class AACPManager {
|
|||||||
EAR_DETECTION_CONFIG(0x0A), AUTOMATIC_CONNECTION_CONFIG(0x20), OWNS_CONNECTION(0x06), PPE_TOGGLE_CONFIG(
|
EAR_DETECTION_CONFIG(0x0A), AUTOMATIC_CONNECTION_CONFIG(0x20), OWNS_CONNECTION(0x06), PPE_TOGGLE_CONFIG(
|
||||||
0x37
|
0x37
|
||||||
),
|
),
|
||||||
PPE_CAP_LEVEL_CONFIG(0x38);
|
PPE_CAP_LEVEL_CONFIG(0x38),
|
||||||
|
DYNAMIC_END_OF_CHARGE(0x3B);
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromByte(byte: Byte): ControlCommandIdentifiers? =
|
fun fromByte(byte: Byte): ControlCommandIdentifiers? =
|
||||||
|
|||||||
@@ -398,6 +398,16 @@ fun AirPodsSettingsScreen(viewModel: AirPodsViewModel, navController: NavControl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
item(key = "spacer_dynamic_end_of_charge") { Spacer(modifier = Modifier.height(16.dp)) }
|
||||||
|
item(key = "dynamic_end_of_charge") {
|
||||||
|
StyledToggle(
|
||||||
|
label = stringResource(R.string.optimized_charging),
|
||||||
|
description = stringResource(R.string.optimized_charging_description),
|
||||||
|
checked = state.dynamicEndOfCharge,
|
||||||
|
onCheckedChange = viewModel::setDynamicEndOfCharge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
item(key = "spacer_accessibility") { Spacer(modifier = Modifier.height(16.dp)) }
|
item(key = "spacer_accessibility") { Spacer(modifier = Modifier.height(16.dp)) }
|
||||||
item(key = "accessibility") {
|
item(key = "accessibility") {
|
||||||
NavigationButton(
|
NavigationButton(
|
||||||
@@ -542,19 +552,22 @@ fun AirPodsSettingsScreen(viewModel: AirPodsViewModel, navController: NavControl
|
|||||||
}
|
}
|
||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
}
|
}
|
||||||
StyledButton(
|
if (state.connectionSuccessful) {
|
||||||
onClick = {
|
StyledButton(
|
||||||
viewModel.reconnectFromSavedMac()
|
onClick = {
|
||||||
}, backdrop = backdrop, modifier = Modifier.fillMaxWidth(0.9f)
|
viewModel.reconnectFromSavedMac()
|
||||||
) {
|
}, backdrop = backdrop, modifier = Modifier.fillMaxWidth(0.9f)
|
||||||
Text(
|
) {
|
||||||
text = stringResource(R.string.reconnect_to_last_device), style = TextStyle(
|
Text(
|
||||||
fontSize = 16.sp,
|
text = stringResource(R.string.reconnect_to_last_device),
|
||||||
fontWeight = FontWeight.Medium,
|
style = TextStyle(
|
||||||
fontFamily = FontFamily(Font(R.font.sf_pro)),
|
fontSize = 16.sp,
|
||||||
color = if (isSystemInDarkTheme()) Color.White else Color.Black
|
fontWeight = FontWeight.Medium,
|
||||||
|
fontFamily = FontFamily(Font(R.font.sf_pro)),
|
||||||
|
color = if (isSystemInDarkTheme()) Color.White else Color.Black
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,11 @@ data class AirPodsUiState(
|
|||||||
val hearingAidData: ByteArray = byteArrayOf(),
|
val hearingAidData: ByteArray = byteArrayOf(),
|
||||||
|
|
||||||
val isPremium: Boolean = false,
|
val isPremium: Boolean = false,
|
||||||
val vendorIdHook: Boolean = false
|
val vendorIdHook: Boolean = false,
|
||||||
|
|
||||||
|
val dynamicEndOfCharge: Boolean = false,
|
||||||
|
|
||||||
|
val connectionSuccessful: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
||||||
class AirPodsViewModel(
|
class AirPodsViewModel(
|
||||||
@@ -268,9 +272,16 @@ class AirPodsViewModel(
|
|||||||
val current = state.controlStates[identifier]
|
val current = state.controlStates[identifier]
|
||||||
if (current?.contentEquals(value) == true) return@update state
|
if (current?.contentEquals(value) == true) return@update state
|
||||||
|
|
||||||
state.copy(
|
if (identifier == ControlCommandIdentifiers.DYNAMIC_END_OF_CHARGE) {
|
||||||
controlStates = state.controlStates + (identifier to value)
|
state.copy(
|
||||||
)
|
dynamicEndOfCharge = value[0] == 0x01.toByte(),
|
||||||
|
controlStates = state.controlStates + (identifier to value)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
state.copy(
|
||||||
|
controlStates = state.controlStates + (identifier to value)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,6 +316,7 @@ class AirPodsViewModel(
|
|||||||
ControlCommandIdentifiers.AUTOMATIC_CONNECTION_CONFIG,
|
ControlCommandIdentifiers.AUTOMATIC_CONNECTION_CONFIG,
|
||||||
ControlCommandIdentifiers.OWNS_CONNECTION,
|
ControlCommandIdentifiers.OWNS_CONNECTION,
|
||||||
ControlCommandIdentifiers.PPE_TOGGLE_CONFIG,
|
ControlCommandIdentifiers.PPE_TOGGLE_CONFIG,
|
||||||
|
ControlCommandIdentifiers.DYNAMIC_END_OF_CHARGE
|
||||||
)
|
)
|
||||||
for (identifier in identifiersList) {
|
for (identifier in identifiersList) {
|
||||||
observeControl(identifier)
|
observeControl(identifier)
|
||||||
@@ -342,6 +354,9 @@ class AirPodsViewModel(
|
|||||||
) ?: "CYCLE_NOISE_CONTROL_MODES"
|
) ?: "CYCLE_NOISE_CONTROL_MODES"
|
||||||
)
|
)
|
||||||
val vendorIdHook = xposedRemotePref.getBoolean("vendor_id_hook", false)
|
val vendorIdHook = xposedRemotePref.getBoolean("vendor_id_hook", false)
|
||||||
|
val dynamicEndOfCharge = sharedPreferences.getBoolean("dynamic_end_of_charge", false)
|
||||||
|
|
||||||
|
val connectionSuccessful = sharedPreferences.getBoolean("connection_successful", false)
|
||||||
|
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
@@ -351,7 +366,9 @@ class AirPodsViewModel(
|
|||||||
headGesturesEnabled = headGesturesEnabled,
|
headGesturesEnabled = headGesturesEnabled,
|
||||||
leftAction = leftAction,
|
leftAction = leftAction,
|
||||||
rightAction = rightAction,
|
rightAction = rightAction,
|
||||||
vendorIdHook = vendorIdHook
|
vendorIdHook = vendorIdHook,
|
||||||
|
dynamicEndOfCharge = dynamicEndOfCharge,
|
||||||
|
connectionSuccessful = connectionSuccessful
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -371,6 +388,14 @@ class AirPodsViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setDynamicEndOfCharge(enabled: Boolean) {
|
||||||
|
service.aacpManager.sendControlCommand(ControlCommandIdentifiers.DYNAMIC_END_OF_CHARGE.value, enabled)
|
||||||
|
sharedPreferences.edit { putBoolean("dynamic_end_of_charge", enabled) }
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(dynamicEndOfCharge = enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun loadControlList() {
|
private fun loadControlList() {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
|||||||
@@ -526,7 +526,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
|
|
||||||
initializeConfig()
|
initializeConfig()
|
||||||
|
|
||||||
ancModeReceiver = object : BroadcastReceiver() {
|
externalBroadcastReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
if (intent?.action == "me.kavishdevar.librepods.SET_ANC_MODE") {
|
if (intent?.action == "me.kavishdevar.librepods.SET_ANC_MODE") {
|
||||||
if (intent.hasExtra("mode")) {
|
if (intent.hasExtra("mode")) {
|
||||||
@@ -555,15 +555,23 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
"Cycling ANC mode from $currentMode to $nextMode"
|
"Cycling ANC mode from $currentMode to $nextMode"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else if (intent?.action == "me.kavishdevar.librepods.CONVO_DETECT") {
|
||||||
|
if (intent.hasExtra("enabled")) {
|
||||||
|
val enabled = intent.getBooleanExtra("enabled", false)
|
||||||
|
aacpManager.sendControlCommand(
|
||||||
|
AACPManager.Companion.ControlCommandIdentifiers.CONVERSATION_DETECT_CONFIG.value,
|
||||||
|
enabled
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
registerReceiver(ancModeReceiver, ancModeFilter, RECEIVER_EXPORTED)
|
registerReceiver(externalBroadcastReceiver, externalBroadcastFilter, RECEIVER_EXPORTED)
|
||||||
} else {
|
} else {
|
||||||
@Suppress("UnspecifiedRegisterReceiverFlag") registerReceiver(
|
@Suppress("UnspecifiedRegisterReceiverFlag") registerReceiver(
|
||||||
ancModeReceiver, ancModeFilter
|
externalBroadcastReceiver, externalBroadcastFilter
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val audioManager = this@AirPodsService.getSystemService(AUDIO_SERVICE) as AudioManager
|
val audioManager = this@AirPodsService.getSystemService(AUDIO_SERVICE) as AudioManager
|
||||||
@@ -2397,8 +2405,11 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val ancModeFilter = IntentFilter("me.kavishdevar.librepods.SET_ANC_MODE")
|
val externalBroadcastFilter = IntentFilter().apply {
|
||||||
var ancModeReceiver: BroadcastReceiver? = null
|
addAction("me.kavishdevar.librepods.SET_ANC_MODE")
|
||||||
|
addAction("me.kavishdevar.librepods.CONVO_DETECT")
|
||||||
|
}
|
||||||
|
var externalBroadcastReceiver: BroadcastReceiver? = null
|
||||||
|
|
||||||
@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 {
|
||||||
@@ -3104,7 +3115,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
unregisterReceiver(ancModeReceiver)
|
unregisterReceiver(externalBroadcastReceiver)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ fun isSupported(sharedPreferences: SharedPreferences): Boolean {
|
|||||||
if (isPixel) {
|
if (isPixel) {
|
||||||
when (Build.VERSION.SDK_INT) {
|
when (Build.VERSION.SDK_INT) {
|
||||||
36 -> {
|
36 -> {
|
||||||
return Build.ID == "CP1A.260305.018" || Build.ID == "CP1A.260405.005"
|
return Build.ID == "CP1A.260305.018" || Build.ID == "CP1A.260405.005" || Build.ID == "CP1A.260505.005"
|
||||||
}
|
}
|
||||||
|
|
||||||
37 -> {
|
37 -> {
|
||||||
|
|||||||
@@ -272,4 +272,6 @@
|
|||||||
<string name="app_enabled_in_xposed">App enabled in Xposed</string>
|
<string name="app_enabled_in_xposed">App enabled in Xposed</string>
|
||||||
<string name="subject">Subject</string>
|
<string name="subject">Subject</string>
|
||||||
<string name="describe_your_issue">Describe your issue</string>
|
<string name="describe_your_issue">Describe your issue</string>
|
||||||
|
<string name="optimized_charging">Optimized Charge Limit</string>
|
||||||
|
<string name="optimized_charging_description">AirPods can learn from your daily usage and determine when to charge to an optmized limit and when to allow or full charge. This limit adapts to your daily usage and preserves your battery lifespan over time.\\nThis setting may not affect unsupported AirPods, or AirPods on an older firmware version.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user