Skip to content
64 changes: 64 additions & 0 deletions app/schemas/de.langerhans.odintools.data.AppDatabase/3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"formatVersion": 1,
"database": {
"version": 3,
"identityHash": "83c89ea9294a5f18c12f79b54aecff49",
"entities": [
{
"tableName": "appoverride",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`packageName` TEXT NOT NULL, `controllerStyle` TEXT, `l2R2Style` TEXT, `perfMode` TEXT, `fanMode` TEXT, `vibrationStrength` INTEGER, PRIMARY KEY(`packageName`))",
"fields": [
{
"fieldPath": "packageName",
"columnName": "packageName",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "controllerStyle",
"columnName": "controllerStyle",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "l2R2Style",
"columnName": "l2R2Style",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "perfMode",
"columnName": "perfMode",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "fanMode",
"columnName": "fanMode",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "vibrationStrength",
"columnName": "vibrationStrength",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"packageName"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '83c89ea9294a5f18c12f79b54aecff49')"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ data class AppUiModel(
val l2r2Style: L2R2Style? = null,
val fanMode: FanMode? = null,
val perfMode: PerfMode? = null,
val vibrationStrength: Int? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,18 @@ class AppOverrideMapper @Inject constructor(
val l2R2Style = L2R2Style.getById(app.l2R2Style)
val perfMode = PerfMode.getById(app.perfMode)
val fanMode = FanMode.getById(app.fanMode)
val vibrationStrength = app.vibrationStrength

return AppUiModel(
packageName = app.packageName,
appName = context.packageManager.getApplicationLabel(appInfo).toString(),
appIcon = context.packageManager.getApplicationIcon(appInfo),
subtitle = getSubtitle(controllerStyle, l2R2Style, perfMode, fanMode),
subtitle = getSubtitle(controllerStyle, l2R2Style, perfMode, fanMode, vibrationStrength),
controllerStyle = controllerStyle,
l2r2Style = l2R2Style,
perfMode = perfMode,
fanMode = fanMode,
vibrationStrength = vibrationStrength,
)
}

Expand All @@ -68,7 +70,13 @@ class AppOverrideMapper @Inject constructor(
)
}

private fun getSubtitle(controllerStyle: ControllerStyle, l2R2Style: L2R2Style, perfMode: PerfMode, fanMode: FanMode): String? {
private fun getSubtitle(
controllerStyle: ControllerStyle,
l2R2Style: L2R2Style,
perfMode: PerfMode,
fanMode: FanMode,
vibrationStrength: Int?,
): String? {
return buildString {
if (controllerStyle != ControllerStyle.Unknown) {
append(context.getString(R.string.controllerStyle))
Expand All @@ -88,6 +96,12 @@ class AppOverrideMapper @Inject constructor(
append(context.getString(perfMode.textRes))
append(" | ")
}
if (vibrationStrength != null) {
append(context.getString(R.string.vibrationStrength))
append(": ")
append(vibrationStrength.toString())
append(" | ")
}
if (fanMode != FanMode.Unknown) {
append(context.getString(R.string.fanMode))
append(": ")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
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.Alignment
Expand All @@ -49,9 +50,11 @@ import de.langerhans.odintools.models.PerfMode
import de.langerhans.odintools.models.PerfMode.HighPerformance
import de.langerhans.odintools.models.PerfMode.Performance
import de.langerhans.odintools.models.PerfMode.Standard
import de.langerhans.odintools.tools.DeviceType
import de.langerhans.odintools.ui.composables.DeleteConfirmDialog
import de.langerhans.odintools.ui.composables.LargeDropdownMenu
import de.langerhans.odintools.ui.composables.OdinTopAppBar
import de.langerhans.odintools.ui.composables.VibrationPreferenceDialog
import de.langerhans.odintools.ui.theme.Typography

@Composable
Expand Down Expand Up @@ -181,6 +184,17 @@ fun AppOverridesScreen(viewModel: AppOverridesViewModel = hiltViewModel(), navig
onSelectionChanged = { viewModel.perfModeSelected(it) },
modifier = Modifier.padding(bottom = 16.dp),
)
if (viewModel.getDeviceType() == DeviceType.ODIN2) {
Column {
VibrationPreferenceRow(
label = stringResource(id = R.string.vibrationStrength),
initialValue = uiState.app?.vibrationStrength ?: 0,
onSave = { newValue ->
viewModel.vibrationStrengthSelected(newValue)
},
)
}
}
if (uiState.app?.perfMode != null && uiState.app?.perfMode != PerfMode.Unknown) {
OverrideSpinnerRow(
label = R.string.fanMode,
Expand Down Expand Up @@ -267,3 +281,37 @@ fun OverrideSpinnerRow(
)
}
}

@Composable
fun VibrationPreferenceRow(label: String, initialValue: Int, onSave: (newValue: Int) -> Unit, modifier: Modifier = Modifier) {
var showDialog by remember { mutableStateOf(false) }

Row(
verticalAlignment = Alignment.CenterVertically,
modifier = modifier
.padding(8.dp),
) {
Text(
text = label,
modifier = Modifier
.weight(1f)
.padding(end = 16.dp),
)
OutlinedButton(
onClick = { showDialog = true },
) {
Text(text = "$initialValue")
}
}

if (showDialog) {
VibrationPreferenceDialog(
initialValue = initialValue,
onCancel = { showDialog = false },
onSave = { newValue ->
onSave(newValue)
showDialog = false
},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import de.langerhans.odintools.models.FanMode.Companion.getDisabledFanModes
import de.langerhans.odintools.models.L2R2Style
import de.langerhans.odintools.models.NoChange
import de.langerhans.odintools.models.PerfMode
import de.langerhans.odintools.tools.DeviceType
import de.langerhans.odintools.tools.DeviceUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
Expand All @@ -37,6 +38,7 @@ class AppOverridesViewModel @Inject constructor(
private var initialControllerStyle = NoChange.KEY
private var initialL2R2Style = NoChange.KEY
private var initialPerfMode = NoChange.KEY
private var initialVibrationStrength: Int? = null
private var initialFanMode = NoChange.KEY

init {
Expand All @@ -51,6 +53,7 @@ class AppOverridesViewModel @Inject constructor(
initialControllerStyle = app.controllerStyle ?: NoChange.KEY
initialL2R2Style = app.l2R2Style ?: NoChange.KEY
initialPerfMode = app.perfMode ?: NoChange.KEY
initialVibrationStrength = app.vibrationStrength ?: null
initialFanMode = app.fanMode ?: NoChange.KEY

appOverrideMapper.mapAppOverride(app)
Expand All @@ -77,6 +80,7 @@ class AppOverridesViewModel @Inject constructor(
l2R2Style = _uiState.value.app?.l2r2Style?.id,
perfMode = _uiState.value.app?.perfMode?.id,
fanMode = _uiState.value.app?.fanMode?.id,
vibrationStrength = _uiState.value.app?.vibrationStrength,
),
)
}
Expand Down Expand Up @@ -161,17 +165,32 @@ class AppOverridesViewModel @Inject constructor(
}
}

fun vibrationStrengthSelected(strength: Int) {
_uiState.update {
it.copy(
app = it.app?.copy(vibrationStrength = strength),
hasUnsavedChanges = hasUnsavedChanges(vibrationStrength = strength),
)
}
}

fun getDeviceType(): DeviceType {
return deviceUtils.getDeviceType()
}

private fun hasUnsavedChanges(
controllerStyle: String? = null,
l2R2Style: String? = null,
perfMode: String? = null,
fanMode: String? = null,
vibrationStrength: Int? = null,
): Boolean {
return listOf(
(controllerStyle ?: _uiState.value.app?.controllerStyle?.id) != initialControllerStyle,
(l2R2Style ?: _uiState.value.app?.l2r2Style?.id) != initialL2R2Style,
(perfMode ?: _uiState.value.app?.perfMode?.id) != initialPerfMode,
(fanMode ?: _uiState.value.app?.fanMode?.id) != initialFanMode,
(vibrationStrength ?: _uiState.value.app?.vibrationStrength) != initialVibrationStrength,
).any { it }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import androidx.room.RoomDatabase

@Database(
entities = [AppOverrideEntity::class],
version = 2,
version = 3,
autoMigrations = [
AutoMigration(from = 1, to = 2),
AutoMigration(from = 2, to = 3),
],
exportSchema = true,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ data class AppOverrideEntity(
val l2R2Style: String?,
val perfMode: String?,
val fanMode: String?,
val vibrationStrength: Int?,
)
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class ForegroundAppWatcherService @Inject constructor() : AccessibilityService()
private var savedL2R2Style: L2R2Style? = null
private var savedPerfMode: PerfMode? = null
private var savedFanMode: FanMode? = null
private var savedVibrationStrength: Int? = null

private var currentIme = ""
private val imeObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
Expand Down Expand Up @@ -100,6 +101,7 @@ class ForegroundAppWatcherService @Inject constructor() : AccessibilityService()
savedL2R2Style = L2R2Style.getStyle(executor)
savedPerfMode = PerfMode.getMode(executor)
savedFanMode = FanMode.getMode(executor)
savedVibrationStrength = executor.getVibrationStrength().getOrNull()
}

ControllerStyle.getById(override.controllerStyle).takeIf {
Expand Down Expand Up @@ -130,6 +132,12 @@ class ForegroundAppWatcherService @Inject constructor() : AccessibilityService()
savedFanMode?.enable(executor)
}

if (override.vibrationStrength != null) {
executor.setVibrationStrength(override.vibrationStrength)
} else {
executor.setVibrationStrength(savedVibrationStrength)
}

hasSetOverride = true
}

Expand All @@ -148,6 +156,9 @@ class ForegroundAppWatcherService @Inject constructor() : AccessibilityService()
savedFanMode?.enable(executor)
savedFanMode = null

executor.setVibrationStrength(savedVibrationStrength)
savedVibrationStrength = null

hasSetOverride = false
}

Expand Down
11 changes: 11 additions & 0 deletions app/src/main/java/de/langerhans/odintools/tools/ShellExecutor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,15 @@ class ShellExecutor @Inject constructor() {
fun setBooleanValue(file: String, value: Boolean) {
setIntValue(file, if (value) 1 else 0)
}

fun getVibrationStrength(): Result<Int> {
val defaultValue = 0
return executeAsRoot("cat /d/haptics/user_vmax_mv")
.mapCatching { it?.toInt() ?: defaultValue } // This throws on RP4 because it has no rumble
}

fun setVibrationStrength(newValue: Int?) {
if (newValue == null) return
executeAsRoot("echo $newValue > /d/haptics/user_vmax_mv")
}
}