mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-05-18 14:56:44 +00:00
Compare commits
6 Commits
nightly-4e
...
nightly-84
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
845f26192c | ||
|
|
3321bb1c43 | ||
|
|
c7a5cb2d8c | ||
|
|
7b81411417 | ||
|
|
d80f2275a1 | ||
|
|
795bebc6ae |
@@ -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 {
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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]
|
||||||
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
package me.kavishdevar.librepods.utils
|
|
||||||
|
|
||||||
object NativeBridge {
|
|
||||||
fun setSdpHook(enabled: Boolean) { }
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user