mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-05-01 18:44:12 +00:00
android: use pressandhold settings when cycling modes
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import java.util.Properties
|
import java.util.Properties
|
||||||
|
|
||||||
val appVersionName = "0.2.6"
|
val appVersionName = "0.2.7"
|
||||||
|
|
||||||
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 = 47
|
||||||
versionName = appVersionName
|
versionName = appVersionName
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1970,7 +1954,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",
|
||||||
@@ -3185,3 +3169,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]
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user