Compare commits

..

6 Commits

Author SHA1 Message Date
Kavish Devar
845f26192c android: make head tracking screen scrollable 2026-04-30 12:53:02 +05:30
Kavish Devar
3321bb1c43 android: bump version 2026-04-30 01:07:43 +05:30
Kavish Devar
c7a5cb2d8c android: fix crash in listening mode widget when service is null 2026-04-30 01:03:51 +05:30
Kavish Devar
7b81411417 android: fix media not resuming when using single AirPod 2026-04-30 01:00:15 +05:30
Kavish Devar
d80f2275a1 android: remove NativeBridge calls from app settings 2026-04-30 00:58:42 +05:30
Kavish Devar
795bebc6ae android: use pressandhold settings when cycling modes 2026-04-28 20:29:00 +05:30
9 changed files with 135 additions and 183 deletions

View File

@@ -1,6 +1,6 @@
import java.util.Properties import java.util.Properties
val appVersionName = "0.2.6" val appVersionName = "0.2.8"
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 = 46 versionCode = 49
versionName = appVersionName versionName = appVersionName
} }
buildTypes { buildTypes {

View File

@@ -99,9 +99,9 @@ import me.kavishdevar.librepods.presentation.components.StyledButton
import me.kavishdevar.librepods.presentation.components.StyledIconButton import me.kavishdevar.librepods.presentation.components.StyledIconButton
import me.kavishdevar.librepods.presentation.components.StyledScaffold import me.kavishdevar.librepods.presentation.components.StyledScaffold
import me.kavishdevar.librepods.presentation.components.StyledToggle import me.kavishdevar.librepods.presentation.components.StyledToggle
import me.kavishdevar.librepods.presentation.viewmodel.AirPodsViewModel
import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.services.ServiceManager
import me.kavishdevar.librepods.utils.HeadTracking import me.kavishdevar.librepods.utils.HeadTracking
import me.kavishdevar.librepods.presentation.viewmodel.AirPodsViewModel
import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.cos import kotlin.math.cos
@@ -151,9 +151,13 @@ fun HeadTrackingScreen(viewModel: AirPodsViewModel, navController: NavController
var lastClickTime by remember { mutableLongStateOf(0L) } var lastClickTime by remember { mutableLongStateOf(0L) }
var shouldExplode by remember { mutableStateOf(false) } var shouldExplode by remember { mutableStateOf(false) }
val scrollState = rememberScrollState()
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth(), .fillMaxWidth()
.verticalScroll(scrollState),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Column ( Column (
@@ -163,7 +167,6 @@ fun HeadTrackingScreen(viewModel: AirPodsViewModel, navController: NavController
.layerBackdrop(backdrop) .layerBackdrop(backdrop)
.padding(top = 8.dp) .padding(top = 8.dp)
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)
.verticalScroll(scrollState)
) { ) {
Spacer(modifier = Modifier.height(topPadding)) Spacer(modifier = Modifier.height(topPadding))

View File

@@ -20,7 +20,6 @@
package me.kavishdevar.librepods.presentation.screens package me.kavishdevar.librepods.presentation.screens
import android.content.Context
import android.util.Log import android.util.Log
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -34,13 +33,8 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
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
@@ -48,19 +42,17 @@ 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.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.core.content.edit
import androidx.navigation.NavController import androidx.navigation.NavController
import com.kyant.backdrop.backdrops.layerBackdrop import com.kyant.backdrop.backdrops.layerBackdrop
import com.kyant.backdrop.backdrops.rememberLayerBackdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import me.kavishdevar.librepods.R import me.kavishdevar.librepods.R
import me.kavishdevar.librepods.bluetooth.AACPManager
import me.kavishdevar.librepods.data.StemAction
import me.kavishdevar.librepods.presentation.components.SelectItem import me.kavishdevar.librepods.presentation.components.SelectItem
import me.kavishdevar.librepods.presentation.components.StyledButton import me.kavishdevar.librepods.presentation.components.StyledButton
import me.kavishdevar.librepods.presentation.components.StyledScaffold import me.kavishdevar.librepods.presentation.components.StyledScaffold
import me.kavishdevar.librepods.presentation.components.StyledSelectList import me.kavishdevar.librepods.presentation.components.StyledSelectList
import me.kavishdevar.librepods.data.StemAction
import me.kavishdevar.librepods.services.ServiceManager
import me.kavishdevar.librepods.bluetooth.AACPManager
import me.kavishdevar.librepods.presentation.viewmodel.AirPodsViewModel import me.kavishdevar.librepods.presentation.viewmodel.AirPodsViewModel
import kotlin.experimental.and import kotlin.experimental.and
import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.io.encoding.ExperimentalEncodingApi
@@ -82,12 +74,7 @@ fun LongPress(viewModel: AirPodsViewModel, name: String, navController: NavContr
Log.d("PressAndHoldSettingsScreen", "Noise Cancellation mode: ${(modesByte and 0x02) != 0.toByte()}") Log.d("PressAndHoldSettingsScreen", "Noise Cancellation mode: ${(modesByte and 0x02) != 0.toByte()}")
Log.d("PressAndHoldSettingsScreen", "Adaptive mode: ${(modesByte and 0x08) != 0.toByte()}") Log.d("PressAndHoldSettingsScreen", "Adaptive mode: ${(modesByte and 0x08) != 0.toByte()}")
val context = LocalContext.current val longPressAction = if (name.lowercase() == "left") state.leftAction else state.rightAction
val sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
val prefKey = if (name.lowercase() == "left") "left_long_press_action" else "right_long_press_action"
val longPressActionPref = sharedPreferences.getString(prefKey, StemAction.CYCLE_NOISE_CONTROL_MODES.name)
Log.d("PressAndHoldSettingsScreen", "Long press action preference ($prefKey): $longPressActionPref")
var longPressAction by remember { mutableStateOf(StemAction.valueOf(longPressActionPref ?: StemAction.CYCLE_NOISE_CONTROL_MODES.name)) }
val backdrop = rememberLayerBackdrop() val backdrop = rememberLayerBackdrop()
StyledScaffold( StyledScaffold(
title = name title = name
@@ -105,16 +92,14 @@ fun LongPress(viewModel: AirPodsViewModel, name: String, navController: NavContr
name = stringResource(R.string.noise_control), name = stringResource(R.string.noise_control),
selected = longPressAction == StemAction.CYCLE_NOISE_CONTROL_MODES, selected = longPressAction == StemAction.CYCLE_NOISE_CONTROL_MODES,
onClick = { onClick = {
longPressAction = StemAction.CYCLE_NOISE_CONTROL_MODES viewModel.setLongPressAction(name, StemAction.CYCLE_NOISE_CONTROL_MODES)
sharedPreferences.edit { putString(prefKey, StemAction.CYCLE_NOISE_CONTROL_MODES.name) }
} }
), ),
SelectItem( SelectItem(
name = stringResource(R.string.digital_assistant), name = stringResource(R.string.digital_assistant),
selected = longPressAction == StemAction.DIGITAL_ASSISTANT, selected = longPressAction == StemAction.DIGITAL_ASSISTANT,
onClick = { onClick = {
longPressAction = StemAction.DIGITAL_ASSISTANT viewModel.setLongPressAction(name, StemAction.DIGITAL_ASSISTANT)
sharedPreferences.edit { putString(prefKey, StemAction.DIGITAL_ASSISTANT.name) }
}, },
enabled = state.isPremium enabled = state.isPremium
) )
@@ -162,21 +147,10 @@ fun LongPress(viewModel: AirPodsViewModel, name: String, navController: NavContr
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
val offListeningModeValue = ServiceManager.getService()!!.aacpManager.controlCommandStatusList.find { val currentByte = state.controlStates[AACPManager.Companion.ControlCommandIdentifiers.LISTENING_MODE_CONFIGS]?.get(0)?.toInt() ?: 0
it.identifier == AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION
}?.value?.takeIf { it.isNotEmpty() }?.get(0)
Log.d("PressAndHoldSettingsScreen", "Allow Off state: $offListeningModeValue")
val allowOff = offListeningModeValue == 1.toByte()
Log.d("PressAndHoldSettingsScreen", "Allow Off option: $allowOff")
val initialByte = state.controlStates[AACPManager.Companion.ControlCommandIdentifiers.LISTENING_MODE_CONFIGS]
?.get(0)?.toInt()
?: sharedPreferences.getInt("long_press_byte", 0b0101)
var currentByte by remember { mutableIntStateOf(initialByte) }
val listeningModeItems = mutableListOf<SelectItem>() val listeningModeItems = mutableListOf<SelectItem>()
if (allowOff) { if (state.offListeningMode) {
listeningModeItems.add( listeningModeItems.add(
SelectItem( SelectItem(
name = stringResource(R.string.off), name = stringResource(R.string.off),
@@ -184,21 +158,7 @@ fun LongPress(viewModel: AirPodsViewModel, name: String, navController: NavContr
iconRes = R.drawable.noise_cancellation, iconRes = R.drawable.noise_cancellation,
selected = (currentByte and 0x01) != 0, selected = (currentByte and 0x01) != 0,
onClick = { onClick = {
val bit = 0x01 viewModel.toggleListeningMode(0x01)
val newValue = if ((currentByte and bit) != 0) {
val temp = currentByte and bit.inv()
if (countEnabledModes(temp) >= 2) temp else currentByte
} else {
currentByte or bit
}
viewModel.setControlCommandByte(
AACPManager.Companion.ControlCommandIdentifiers.LISTENING_MODE_CONFIGS,
newValue.toByte()
)
sharedPreferences.edit {
putInt("long_press_byte", newValue)
}
currentByte = newValue
} }
) )
) )
@@ -210,21 +170,7 @@ fun LongPress(viewModel: AirPodsViewModel, name: String, navController: NavContr
iconRes = R.drawable.transparency, iconRes = R.drawable.transparency,
selected = (currentByte and 0x04) != 0, selected = (currentByte and 0x04) != 0,
onClick = { onClick = {
val bit = 0x04 viewModel.toggleListeningMode(0x04)
val newValue = if ((currentByte and bit) != 0) {
val temp = currentByte and bit.inv()
if (countEnabledModes(temp) >= 2) temp else currentByte
} else {
currentByte or bit
}
viewModel.setControlCommandByte(
AACPManager.Companion.ControlCommandIdentifiers.LISTENING_MODE_CONFIGS,
newValue.toByte()
)
sharedPreferences.edit {
putInt("long_press_byte", newValue)
}
currentByte = newValue
} }
), ),
SelectItem( SelectItem(
@@ -233,21 +179,7 @@ fun LongPress(viewModel: AirPodsViewModel, name: String, navController: NavContr
iconRes = R.drawable.adaptive, iconRes = R.drawable.adaptive,
selected = (currentByte and 0x08) != 0, selected = (currentByte and 0x08) != 0,
onClick = { onClick = {
val bit = 0x08 viewModel.toggleListeningMode(0x08)
val newValue = if ((currentByte and bit) != 0) {
val temp = currentByte and bit.inv()
if (countEnabledModes(temp) >= 2) temp else currentByte
} else {
currentByte or bit
}
viewModel.setControlCommandByte(
AACPManager.Companion.ControlCommandIdentifiers.LISTENING_MODE_CONFIGS,
newValue.toByte()
)
sharedPreferences.edit {
putInt("long_press_byte", newValue)
}
currentByte = newValue
} }
), ),
SelectItem( SelectItem(
@@ -256,21 +188,7 @@ fun LongPress(viewModel: AirPodsViewModel, name: String, navController: NavContr
iconRes = R.drawable.noise_cancellation, iconRes = R.drawable.noise_cancellation,
selected = (currentByte and 0x02) != 0, selected = (currentByte and 0x02) != 0,
onClick = { onClick = {
val bit = 0x02 viewModel.toggleListeningMode(0x02)
val newValue = if ((currentByte and bit) != 0) {
val temp = currentByte and bit.inv()
if (countEnabledModes(temp) >= 2) temp else currentByte
} else {
currentByte or bit
}
viewModel.setControlCommandByte(
AACPManager.Companion.ControlCommandIdentifiers.LISTENING_MODE_CONFIGS,
newValue.toByte()
)
sharedPreferences.edit {
putInt("long_press_byte", newValue)
}
currentByte = newValue
} }
) )
)) ))
@@ -290,14 +208,4 @@ fun LongPress(viewModel: AirPodsViewModel, name: String, navController: NavContr
} }
} }
} }
Log.d("PressAndHoldSettingsScreen", "Current byte: ${modesByte.toString(2)}")
}
fun countEnabledModes(byteValue: Int): Int {
var count = 0
if ((byteValue and 0x01) != 0) count++
if ((byteValue and 0x02) != 0) count++
if ((byteValue and 0x04) != 0) count++
if ((byteValue and 0x08) != 0) count++
return count
} }

View File

@@ -540,6 +540,35 @@ class AirPodsViewModel(
service.aacpManager.sendPhoneMediaEQ(eq, phoneByte, mediaByte) service.aacpManager.sendPhoneMediaEQ(eq, phoneByte, mediaByte)
} }
fun setLongPressAction(side: String, action: StemAction) {
val prefKey = if (side.lowercase() == "left") "left_long_press_action" else "right_long_press_action"
sharedPreferences.edit { putString(prefKey, action.name) }
_uiState.update {
if (side.lowercase() == "left") it.copy(leftAction = action) else it.copy(rightAction = action)
}
}
private fun countEnabledModes(byteValue: Int): Int {
var count = 0
if ((byteValue and 0x01) != 0) count++
if ((byteValue and 0x02) != 0) count++
if ((byteValue and 0x04) != 0) count++
if ((byteValue and 0x08) != 0) count++
return count
}
fun toggleListeningMode(modeBit: Int) {
val currentByte = uiState.value.controlStates[ControlCommandIdentifiers.LISTENING_MODE_CONFIGS]?.get(0)?.toInt() ?: 0
val newValue = if ((currentByte and modeBit) != 0) {
val temp = currentByte and modeBit.inv()
if (countEnabledModes(temp) >= 2) temp else currentByte
} else {
currentByte or modeBit
}
setControlCommandByte(ControlCommandIdentifiers.LISTENING_MODE_CONFIGS, newValue.toByte())
sharedPreferences.edit { putInt("long_press_byte", newValue) }
}
fun disconnect() { fun disconnect() {
service.disconnectAirPods() service.disconnectAirPods()
if (appContext.checkSelfPermission("android.permission.BLUETOOTH_PRIVILEGED") != PackageManager.PERMISSION_GRANTED) { if (appContext.checkSelfPermission("android.permission.BLUETOOTH_PRIVILEGED") != PackageManager.PERMISSION_GRANTED) {

View File

@@ -12,8 +12,6 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.kavishdevar.librepods.billing.BillingManager import me.kavishdevar.librepods.billing.BillingManager
import me.kavishdevar.librepods.data.XposedRemotePrefProvider import me.kavishdevar.librepods.data.XposedRemotePrefProvider
import me.kavishdevar.librepods.utils.NativeBridge
import me.kavishdevar.librepods.utils.XposedState
import kotlin.math.roundToInt import kotlin.math.roundToInt
data class AppSettingsUiState( data class AppSettingsUiState(
@@ -91,9 +89,6 @@ class AppSettingsViewModel(application: Application) : AndroidViewModel(applicat
connectionSuccessful = sharedPreferences.getBoolean("connection_successful", false) connectionSuccessful = sharedPreferences.getBoolean("connection_successful", false)
) )
} }
if (XposedState.isAvailable && XposedState.bluetoothScopeEnabled) {
NativeBridge.setSdpHook(_uiState.value.vendorIdHook)
}
} }
fun setShowPhoneBatteryInWidget(enabled: Boolean) { fun setShowPhoneBatteryInWidget(enabled: Boolean) {
@@ -178,7 +173,6 @@ class AppSettingsViewModel(application: Application) : AndroidViewModel(applicat
} }
fun setVendorIdHook(enabled: Boolean) { fun setVendorIdHook(enabled: Boolean) {
NativeBridge.setSdpHook(enabled)
xposedRemotePref.putBoolean("vendor_id_hook", enabled) xposedRemotePref.putBoolean("vendor_id_hook", enabled)
_uiState.update { it.copy(vendorIdHook = enabled) } _uiState.update { it.copy(vendorIdHook = enabled) }
} }

View File

@@ -28,8 +28,8 @@ import android.content.Intent
import android.util.Log 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.bluetooth.AACPManager import me.kavishdevar.librepods.bluetooth.AACPManager
import me.kavishdevar.librepods.services.ServiceManager
import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.io.encoding.ExperimentalEncodingApi
class NoiseControlWidget : AppWidgetProvider() { class NoiseControlWidget : AppWidgetProvider() {
@@ -82,8 +82,14 @@ class NoiseControlWidget : AppWidgetProvider() {
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") Log.d("NoiseControlWidget", "Setting ANC mode to $mode")
ServiceManager.getService()!! val service = ServiceManager.getService()
.aacpManager
if (service == null) {
Log.w("NoiseControlWidget", "Service unavailable")
return
}
service.aacpManager
.sendControlCommand( .sendControlCommand(
AACPManager.Companion.ControlCommandIdentifiers.LISTENING_MODE.value, AACPManager.Companion.ControlCommandIdentifiers.LISTENING_MODE.value,
mode.toByte() mode.toByte()

View File

@@ -539,28 +539,12 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
} }
} else { } else {
val currentMode = ancNotification.status val currentMode = ancNotification.status
val configByte = sharedPreferences.getInt("long_press_byte", 0b0111)
val allowOffModeValue = val allowOffModeValue =
aacpManager.controlCommandStatusList.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION } aacpManager.controlCommandStatusList.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION }
val allowOffMode = allowOffModeValue?.value?.takeIf { it.isNotEmpty() } val allowOffMode =
?.get(0) == 0x01.toByte() allowOffModeValue?.value?.takeIf { it.isNotEmpty() }?.get(0) == 0x01.toByte() || sharedPreferences.getBoolean("off_listening_mode", true)
val nextMode = getNextMode(currentMode = currentMode, configByte = configByte, allowOffMode)
val nextMode = if (allowOffMode) {
when (currentMode) {
1 -> 2
2 -> 3
3 -> 4
4 -> 1
else -> 1
}
} else {
when (currentMode) {
1 -> 2
2 -> 3
3 -> 4
4 -> 2
else -> 2
}
}
aacpManager.sendControlCommand( aacpManager.sendControlCommand(
AACPManager.Companion.ControlCommandIdentifiers.LISTENING_MODE.value, AACPManager.Companion.ControlCommandIdentifiers.LISTENING_MODE.value,
@@ -568,7 +552,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
) )
Log.d( Log.d(
TAG, TAG,
"Cycling ANC mode from $currentMode to $nextMode (offListeningMode: $allowOffMode)" "Cycling ANC mode from $currentMode to $nextMode"
) )
} }
} }
@@ -1116,7 +1100,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
"AirPodsParser", "AirPodsParser",
"Audio source changed mac: ${aacpManager.audioSource?.mac}, type: ${aacpManager.audioSource?.type?.name}" "Audio source changed mac: ${aacpManager.audioSource?.mac}, type: ${aacpManager.audioSource?.type?.name}"
) )
if (aacpManager.audioSource?.type != AACPManager.Companion.AudioSourceType.NONE && aacpManager.audioSource?.mac != localMac) { if (localMac!="" && (aacpManager.audioSource?.type != AACPManager.Companion.AudioSourceType.NONE && aacpManager.audioSource?.mac != localMac)) {
Log.d( Log.d(
"AirPodsParser", "AirPodsParser",
"Audio source is another device, better to give up aacp control" "Audio source is another device, better to give up aacp control"
@@ -1272,6 +1256,14 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
disconnectAudio(this@AirPodsService, device) disconnectAudio(this@AirPodsService, device)
} }
} }
val wasNone = inEarData == listOf(false, false)
val nowSingle = newInEarData.count { it } == 1
if (wasNone && nowSingle) {
MediaController.sendPlay()
MediaController.iPausedTheMedia = false
return
}
if (inEarData.contains(false) && newInEarData == listOf(true, true)) { if (inEarData.contains(false) && newInEarData == listOf(true, true)) {
Log.d("AirPodsParser", "User put in both AirPods from just one.") Log.d("AirPodsParser", "User put in both AirPods from just one.")
@@ -1970,7 +1962,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
val allowOffModeValue = val allowOffModeValue =
aacpManager.controlCommandStatusList.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION } aacpManager.controlCommandStatusList.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION }
val allowOffMode = val allowOffMode =
allowOffModeValue?.value?.takeIf { it.isNotEmpty() }?.get(0) == 0x01.toByte() allowOffModeValue?.value?.takeIf { it.isNotEmpty() }?.get(0) == 0x01.toByte() || sharedPreferences.getBoolean("off_listening_mode", true)
it.setInt( it.setInt(
R.id.widget_off_button, R.id.widget_off_button,
"setBackgroundResource", "setBackgroundResource",
@@ -3005,22 +2997,20 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
fun connectAudio(context: Context, device: BluetoothDevice?) { fun connectAudio(context: Context, device: BluetoothDevice?) {
val bluetoothAdapter = context.getSystemService(BluetoothManager::class.java).adapter val bluetoothAdapter = context.getSystemService(BluetoothManager::class.java).adapter
bluetoothAdapter?.getProfileProxy(context, object : BluetoothProfile.ServiceListener {
override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) { bluetoothAdapter?.getProfileProxy(context, object : BluetoothProfile.ServiceListener {
if (profile == BluetoothProfile.A2DP) { override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
if (profile == BluetoothProfile.A2DP) {
if (context.checkSelfPermission("android.permission.BLUETOOTH_PRIVILEGED") == PackageManager.PERMISSION_GRANTED) {
try { try {
if (context.checkSelfPermission("android.permission.BLUETOOTH_PRIVILEGED") == PackageManager.PERMISSION_GRANTED) { val policyMethod = proxy.javaClass.getMethod(
val policyMethod = proxy.javaClass.getMethod( "setConnectionPolicy",
"setConnectionPolicy", BluetoothDevice::class.java,
BluetoothDevice::class.java, Int::class.java
Int::class.java )
) Log.d(TAG, "calling A2DP.setConnectionPolicy for ${device?.address} to 100")
Log.d(TAG, "calling A2DP.setConnectionPolicy for ${device?.address} to 100") policyMethod.invoke(proxy, device, 100)
policyMethod.invoke(proxy, device, 100)
}
else {
Log.d(TAG, "not setting connection policy for A2DP, no BLUETOOTH_PRIVILEGED permission")
}
val connectMethod = val connectMethod =
proxy.javaClass.getMethod("connect", BluetoothDevice::class.java) proxy.javaClass.getMethod("connect", BluetoothDevice::class.java)
connectMethod.invoke( connectMethod.invoke(
@@ -3035,30 +3025,35 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
} }
} }
} }
else {
val connectMethod =
proxy.javaClass.getMethod("connect", BluetoothDevice::class.java)
connectMethod.invoke(
proxy, device
)
Log.d(TAG, "not setting connection policy for A2DP, no BLUETOOTH_PRIVILEGED permission. just called connect")
}
} }
}
override fun onServiceDisconnected(profile: Int) {} override fun onServiceDisconnected(profile: Int) {}
}, BluetoothProfile.A2DP) }, BluetoothProfile.A2DP)
bluetoothAdapter?.getProfileProxy(context, object : BluetoothProfile.ServiceListener { bluetoothAdapter?.getProfileProxy(context, object : BluetoothProfile.ServiceListener {
override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) { override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
if (profile == BluetoothProfile.HEADSET) { if (profile == BluetoothProfile.HEADSET) {
if (checkSelfPermission("android.permission.MODIFY_PHONE_STATE") == PackageManager.PERMISSION_GRANTED) {
try { try {
if (checkSelfPermission("android.permission.MODIFY_PHONE_STATE") == PackageManager.PERMISSION_GRANTED) { val policyMethod = proxy.javaClass.getMethod(
"setConnectionPolicy",
val policyMethod = proxy.javaClass.getMethod( BluetoothDevice::class.java,
"setConnectionPolicy", Int::class.java
BluetoothDevice::class.java, )
Int::class.java Log.d(
) TAG,
Log.d( "calling HEADSET.setConnectionPolicy for ${device?.address} to 100"
TAG, )
"calling HEADSET.setConnectionPolicy for ${device?.address} to 100" policyMethod.invoke(proxy, device, 100)
)
policyMethod.invoke(proxy, device, 100)
} else {
Log.d(TAG, "not setting connection policy for HEADSET, no MODIFIY_PHONE_STATE permission")
}
val connectMethod = val connectMethod =
proxy.javaClass.getMethod("connect", BluetoothDevice::class.java) proxy.javaClass.getMethod("connect", BluetoothDevice::class.java)
connectMethod.invoke(proxy, device) connectMethod.invoke(proxy, device)
@@ -3067,11 +3062,14 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
} finally { } finally {
bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, proxy) bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, proxy)
} }
} else {
Log.d(TAG, "not setting connection policy for HEADSET, no MODIFIY_PHONE_STATE permission")
} }
} }
}
override fun onServiceDisconnected(profile: Int) {} override fun onServiceDisconnected(profile: Int) {}
}, BluetoothProfile.HEADSET) }, BluetoothProfile.HEADSET)
} }
fun setName(name: String) { fun setName(name: String) {
@@ -3185,3 +3183,20 @@ private fun Int.dpToPx(): Int {
val density = Resources.getSystem().displayMetrics.density val density = Resources.getSystem().displayMetrics.density
return (this * density).toInt() return (this * density).toInt()
} }
fun getNextMode(currentMode: Int, configByte: Int, offmodeEnabled: Boolean): Int {
val enabledModes = buildList {
if ((configByte and 0x01) != 0 && offmodeEnabled) add(1)
if ((configByte and 0x04) != 0) add(3)
if ((configByte and 0x08) != 0) add(4)
if ((configByte and 0x02) != 0) add(2)
}
Log.d(TAG, "currentMode: $currentMode, config: ${configByte.toString(2)}")
if (enabledModes.isEmpty()) return currentMode
val currentIndex = enabledModes.indexOf(currentMode)
val nextIndex = if (currentIndex == -1) 0 else (currentIndex + 1) % enabledModes.size
return enabledModes[nextIndex]
}

View File

@@ -171,8 +171,10 @@ object MediaController {
} }
if (configs != null && !iPausedTheMedia) { if (configs != null && !iPausedTheMedia) {
val localMac = ServiceManager.getService()?.localMac ?: return
if (localMac == "") return
ServiceManager.getService()?.aacpManager?.sendMediaInformataion( ServiceManager.getService()?.aacpManager?.sendMediaInformataion(
ServiceManager.getService()?.localMac ?: return, localMac,
isActive isActive
) )
Log.d("MediaController", "User changed media state themselves; will wait for ear detection pause before auto-play") Log.d("MediaController", "User changed media state themselves; will wait for ear detection pause before auto-play")

View File

@@ -1,5 +0,0 @@
package me.kavishdevar.librepods.utils
object NativeBridge {
fun setSdpHook(enabled: Boolean) { }
}