diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..07764a7
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+* text eol=lf
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index a6473b8..42268b9 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -50,6 +50,9 @@ android {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
+ hilt {
+ enableAggregatingTask = true
+ }
}
room {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6e68d4b..031adee 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,73 +1,81 @@
-
+
-
-
+
+
+
+
+ android:name=".OdinToolsApplication"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/appName"
+ android:supportsRtl="true"
+ android:theme="@style/Theme.OdinTools"
+ android:dataExtractionRules="@xml/data_extraction_rules">
+ android:name=".main.MainActivity"
+ android:exported="true"
+ android:theme="@style/Theme.OdinTools"
+ android:screenOrientation="landscape">
-
+
-
+
+ android:name=".tiles.ControllerTileService"
+ android:exported="true"
+ android:icon="@drawable/ic_face_buttons"
+ android:label="@string/controllerStyle"
+ android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
-
+
+ android:name="android.service.quicksettings.TOGGLEABLE_TILE"
+ android:value="true" />
+ android:name=".tiles.L2R2TileService"
+ android:exported="true"
+ android:icon="@drawable/ic_sliders"
+ android:label="@string/l2r2mode"
+ android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
-
+
+ android:name="android.service.quicksettings.TOGGLEABLE_TILE"
+ android:value="true" />
-
+
-
+
-
+
+
+ android:name=".tools.BootReceiver"
+ android:enabled="true"
+ android:exported="true">
-
+
-
\ No newline at end of file
+
diff --git a/app/src/main/java/de/langerhans/odintools/appsettings/AppOverrideListScreen.kt b/app/src/main/java/de/langerhans/odintools/appsettings/AppOverrideListScreen.kt
index 9d0fe26..fade0f0 100644
--- a/app/src/main/java/de/langerhans/odintools/appsettings/AppOverrideListScreen.kt
+++ b/app/src/main/java/de/langerhans/odintools/appsettings/AppOverrideListScreen.kt
@@ -3,10 +3,19 @@ package de.langerhans.odintools.appsettings
import android.graphics.drawable.Drawable
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
-import androidx.compose.material3.*
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
diff --git a/app/src/main/java/de/langerhans/odintools/appsettings/AppOverrideListViewModel.kt b/app/src/main/java/de/langerhans/odintools/appsettings/AppOverrideListViewModel.kt
index acf9405..83e40a4 100644
--- a/app/src/main/java/de/langerhans/odintools/appsettings/AppOverrideListViewModel.kt
+++ b/app/src/main/java/de/langerhans/odintools/appsettings/AppOverrideListViewModel.kt
@@ -6,7 +6,11 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import de.langerhans.odintools.data.AppOverrideDao
import de.langerhans.odintools.tools.DeviceUtils
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject
diff --git a/app/src/main/java/de/langerhans/odintools/appsettings/AppOverridesScreen.kt b/app/src/main/java/de/langerhans/odintools/appsettings/AppOverridesScreen.kt
index 9faee47..689b94a 100644
--- a/app/src/main/java/de/langerhans/odintools/appsettings/AppOverridesScreen.kt
+++ b/app/src/main/java/de/langerhans/odintools/appsettings/AppOverridesScreen.kt
@@ -2,11 +2,29 @@ package de.langerhans.odintools.appsettings
import androidx.annotation.StringRes
import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.*
-import androidx.compose.runtime.*
+import androidx.compose.material3.Button
+import androidx.compose.material3.FilledTonalButton
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.VerticalDivider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -16,12 +34,21 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.google.accompanist.drawablepainter.rememberDrawablePainter
import de.langerhans.odintools.R
-import de.langerhans.odintools.models.ControllerStyle.*
-import de.langerhans.odintools.models.FanMode.*
-import de.langerhans.odintools.models.L2R2Style.*
+import de.langerhans.odintools.models.ControllerStyle.Disconnect
+import de.langerhans.odintools.models.ControllerStyle.Odin
+import de.langerhans.odintools.models.ControllerStyle.Xbox
+import de.langerhans.odintools.models.FanMode.Off
+import de.langerhans.odintools.models.FanMode.Quiet
+import de.langerhans.odintools.models.FanMode.Smart
+import de.langerhans.odintools.models.FanMode.Sport
+import de.langerhans.odintools.models.L2R2Style.Analog
+import de.langerhans.odintools.models.L2R2Style.Both
+import de.langerhans.odintools.models.L2R2Style.Digital
import de.langerhans.odintools.models.NoChange
import de.langerhans.odintools.models.PerfMode
-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.ui.composables.DeleteConfirmDialog
import de.langerhans.odintools.ui.composables.LargeDropdownMenu
import de.langerhans.odintools.ui.composables.OdinTopAppBar
diff --git a/app/src/main/java/de/langerhans/odintools/appsettings/AppOverridesViewModel.kt b/app/src/main/java/de/langerhans/odintools/appsettings/AppOverridesViewModel.kt
index f280fa2..6751dfb 100644
--- a/app/src/main/java/de/langerhans/odintools/appsettings/AppOverridesViewModel.kt
+++ b/app/src/main/java/de/langerhans/odintools/appsettings/AppOverridesViewModel.kt
@@ -6,8 +6,12 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import de.langerhans.odintools.data.AppOverrideDao
import de.langerhans.odintools.data.AppOverrideEntity
-import de.langerhans.odintools.models.*
+import de.langerhans.odintools.models.ControllerStyle
+import de.langerhans.odintools.models.FanMode
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.DeviceUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
diff --git a/app/src/main/java/de/langerhans/odintools/data/AppOverrideDao.kt b/app/src/main/java/de/langerhans/odintools/data/AppOverrideDao.kt
index 3dc1520..ea77f83 100644
--- a/app/src/main/java/de/langerhans/odintools/data/AppOverrideDao.kt
+++ b/app/src/main/java/de/langerhans/odintools/data/AppOverrideDao.kt
@@ -1,6 +1,9 @@
package de.langerhans.odintools.data
-import androidx.room.*
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
import kotlinx.coroutines.flow.Flow
@Dao
diff --git a/app/src/main/java/de/langerhans/odintools/data/SharedPrefsRepo.kt b/app/src/main/java/de/langerhans/odintools/data/SharedPrefsRepo.kt
index 14773c4..685e3da 100644
--- a/app/src/main/java/de/langerhans/odintools/data/SharedPrefsRepo.kt
+++ b/app/src/main/java/de/langerhans/odintools/data/SharedPrefsRepo.kt
@@ -6,7 +6,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
class SharedPrefsRepo @Inject constructor(
- @ApplicationContext context: Context,
+ @ApplicationContext private val context: Context,
) {
private val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
@@ -35,14 +35,45 @@ class SharedPrefsRepo @Inject constructor(
get() = prefs.getBoolean(KEY_OVERRIDE_DELAY, false)
set(value) = prefs.edit().putBoolean(KEY_OVERRIDE_DELAY, value).apply()
+ var chargeLimitEnabled
+ get() = prefs.getBoolean(KEY_CHARGE_LIMIT_ENABLED, false)
+ set(value) = prefs.edit().putBoolean(KEY_CHARGE_LIMIT_ENABLED, value).apply()
+
+ var minBatteryLevel
+ get() = prefs.getInt(KEY_MIN_BATTERY_LEVEL, 20)
+ set(value) = prefs.edit().putInt(KEY_MIN_BATTERY_LEVEL, value).apply()
+
+ var maxBatteryLevel
+ get() = prefs.getInt(KEY_MAX_BATTERY_LEVEL, 80)
+ set(value) = prefs.edit().putInt(KEY_MAX_BATTERY_LEVEL, value).apply()
+
+ private var chargeLimitEnabledListener: OnSharedPreferenceChangeListener? = null
+
+ fun observeChargeLimitEnabledState(onChargeLimitEnabled: (newState: Boolean) -> Unit) {
+ chargeLimitEnabledListener = OnSharedPreferenceChangeListener { _, key ->
+ if (key == KEY_CHARGE_LIMIT_ENABLED) {
+ onChargeLimitEnabled(chargeLimitEnabled)
+ }
+ }
+ prefs.registerOnSharedPreferenceChangeListener(chargeLimitEnabledListener)
+ }
+
+ fun removeChargeLimitEnabledObserver() {
+ prefs.unregisterOnSharedPreferenceChangeListener(chargeLimitEnabledListener)
+ chargeLimitEnabledListener = null
+ }
+
private var appOverrideEnabledListener: OnSharedPreferenceChangeListener? = null
- fun observeAppOverrideEnabledState(overridesEnabled: (newState: Boolean) -> Unit, overrideDelayEnabled: (newState: Boolean) -> Unit) {
+ fun observeAppOverrideEnabledState(
+ onAppOverridesEnabled: (newState: Boolean) -> Unit,
+ onOverrideDelayEnabled: (newState: Boolean) -> Unit,
+ ) {
appOverrideEnabledListener = OnSharedPreferenceChangeListener { _, key ->
if (key == KEY_APP_OVERRIDE_ENABLED) {
- overridesEnabled(appOverridesEnabled)
+ onAppOverridesEnabled(appOverridesEnabled)
} else if (key == KEY_OVERRIDE_DELAY) {
- overrideDelayEnabled(overrideDelay)
+ onOverrideDelayEnabled(overrideDelay)
}
}
prefs.registerOnSharedPreferenceChangeListener(appOverrideEnabledListener)
@@ -62,5 +93,8 @@ class SharedPrefsRepo @Inject constructor(
private const val KEY_VIBRATION_STRENGTH = "vibration_strength"
private const val KEY_APP_OVERRIDE_ENABLED = "app_override_enabled"
private const val KEY_OVERRIDE_DELAY = "override_delay"
+ private const val KEY_CHARGE_LIMIT_ENABLED = "charge_limit_enabled"
+ private const val KEY_MIN_BATTERY_LEVEL = "min_battery_level"
+ private const val KEY_MAX_BATTERY_LEVEL = "max_battery_level"
}
}
diff --git a/app/src/main/java/de/langerhans/odintools/main/MainActivity.kt b/app/src/main/java/de/langerhans/odintools/main/MainActivity.kt
index 8a260a0..ff24b77 100644
--- a/app/src/main/java/de/langerhans/odintools/main/MainActivity.kt
+++ b/app/src/main/java/de/langerhans/odintools/main/MainActivity.kt
@@ -30,12 +30,23 @@ import de.langerhans.odintools.R
import de.langerhans.odintools.appsettings.AppOverrideListScreen
import de.langerhans.odintools.appsettings.AppOverridesScreen
import de.langerhans.odintools.tools.DeviceType.ODIN2
-import de.langerhans.odintools.ui.composables.*
+import de.langerhans.odintools.tools.SettingsRepo
+import de.langerhans.odintools.ui.composables.ChargeLimitPreferenceDialog
+import de.langerhans.odintools.ui.composables.CheckBoxDialogPreference
+import de.langerhans.odintools.ui.composables.NotAnOdinDialog
+import de.langerhans.odintools.ui.composables.OdinTopAppBar
+import de.langerhans.odintools.ui.composables.PServerNotAvailableDialog
+import de.langerhans.odintools.ui.composables.RemapButtonDialog
+import de.langerhans.odintools.ui.composables.SaturationPreferenceDialog
+import de.langerhans.odintools.ui.composables.SettingsHeader
+import de.langerhans.odintools.ui.composables.SwitchPreference
+import de.langerhans.odintools.ui.composables.SwitchableTriggerPreference
+import de.langerhans.odintools.ui.composables.TriggerPreference
+import de.langerhans.odintools.ui.composables.VibrationPreferenceDialog
import de.langerhans.odintools.ui.theme.OdinToolsTheme
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
@@ -68,13 +79,14 @@ fun SettingsScreen(viewModel: MainViewModel = hiltViewModel(), navigateToOverrid
if (uiState.showPServerNotAvailableDialog) {
PServerNotAvailableDialog()
- } else if (uiState.showNotAnOdinDialog) {
+ } else if (uiState.showIncompatibleDeviceDialog) {
NotAnOdinDialog { viewModel.incompatibleDeviceDialogDismissed() }
}
if (uiState.showControllerStyleDialog) {
CheckBoxDialogPreference(
items = viewModel.controllerStyleOptions,
+ title = R.string.controllerStyle,
onCancel = {
viewModel.hideControllerStylePreference()
},
@@ -87,6 +99,7 @@ fun SettingsScreen(viewModel: MainViewModel = hiltViewModel(), navigateToOverrid
if (uiState.showL2r2StyleDialog) {
CheckBoxDialogPreference(
items = viewModel.l2r2StyleOptions,
+ title = R.string.l2r2mode,
onCancel = {
viewModel.hideL2r2StylePreference()
},
@@ -121,6 +134,14 @@ fun SettingsScreen(viewModel: MainViewModel = hiltViewModel(), navigateToOverrid
)
}
+ if (uiState.showChargeLimitDialog) {
+ ChargeLimitPreferenceDialog(
+ initialValue = uiState.currentChargeLimit,
+ onCancel = { viewModel.chargeLimitDialogDismissed() },
+ onSave = { viewModel.saveChargeLimit(it) },
+ )
+ }
+
Scaffold(topBar = { OdinTopAppBar(deviceVersion = uiState.deviceVersion) }) { contentPadding ->
Column(
modifier = Modifier
@@ -141,14 +162,14 @@ fun SettingsScreen(viewModel: MainViewModel = hiltViewModel(), navigateToOverrid
SwitchPreference(
icon = R.drawable.ic_more_time,
title = R.string.overrideDelay,
- description = R.string.overrideDelayDesc,
+ description = R.string.overrideDelayDescription,
state = uiState.overrideDelayEnabled,
) {
viewModel.overrideDelayEnabled(it)
}
- SettingsHeader(R.string.quicksettings)
+ SettingsHeader(R.string.quickSettings)
TriggerPreference(
- icon = R.drawable.ic_controllerstyle,
+ icon = R.drawable.ic_face_buttons,
title = R.string.controllerStyle,
description = R.string.controllerStyleDesc,
) {
@@ -164,11 +185,11 @@ fun SettingsScreen(viewModel: MainViewModel = hiltViewModel(), navigateToOverrid
SettingsHeader(R.string.buttons)
SwitchPreference(
icon = R.drawable.ic_home,
- title = R.string.doubleHomeTitle,
- description = R.string.doubleHomeDescription,
- state = uiState.singleHomeEnabled,
+ title = R.string.singlePressHome,
+ description = R.string.singlePressHomeDescription,
+ state = uiState.singlePressHomeEnabled,
) {
- viewModel.updateSingleHomePreference(it)
+ viewModel.updateSinglePressHomePreference(it)
}
if (uiState.deviceType == ODIN2) {
TriggerPreference(
@@ -176,14 +197,14 @@ fun SettingsScreen(viewModel: MainViewModel = hiltViewModel(), navigateToOverrid
title = R.string.m1Button,
description = R.string.remapButtonDescription,
) {
- viewModel.remapButtonClicked("remap_custom_to_m1_value")
+ viewModel.remapButtonClicked(SettingsRepo.KEY_CUSTOM_M1_VALUE)
}
TriggerPreference(
icon = R.drawable.ic_gamepad,
title = R.string.m2Button,
description = R.string.remapButtonDescription,
) {
- viewModel.remapButtonClicked("remap_custom_to_m2_value")
+ viewModel.remapButtonClicked(SettingsRepo.KEY_CUSTOM_M2_VALUE)
}
}
SettingsHeader(name = R.string.display)
@@ -205,6 +226,24 @@ fun SettingsScreen(viewModel: MainViewModel = hiltViewModel(), navigateToOverrid
) {
viewModel.updateVibrationPreference(it)
}
+ SettingsHeader(R.string.battery)
+ SwitchableTriggerPreference(
+ icon = R.drawable.ic_electrical_services,
+ title = R.string.chargeLimit,
+ description = R.string.chargeLimitDescription,
+ state = uiState.chargeLimitEnabled,
+ onClick = { viewModel.chargeLimitClicked() },
+ ) {
+ viewModel.updateChargeLimitPreference(it)
+ }
+ }
+ SettingsHeader(R.string.debug)
+ TriggerPreference(
+ icon = R.drawable.ic_file_save,
+ title = R.string.dumpLogToFile,
+ description = R.string.dumpLogToFileDescription,
+ ) {
+ viewModel.dumpLogToFile()
}
// Navigation bar padding
diff --git a/app/src/main/java/de/langerhans/odintools/main/MainUiModel.kt b/app/src/main/java/de/langerhans/odintools/main/MainUiModel.kt
index 76c6e08..b487896 100644
--- a/app/src/main/java/de/langerhans/odintools/main/MainUiModel.kt
+++ b/app/src/main/java/de/langerhans/odintools/main/MainUiModel.kt
@@ -10,10 +10,10 @@ import de.langerhans.odintools.tools.DeviceType.ODIN2
data class MainUiModel(
val deviceType: DeviceType = ODIN2,
val deviceVersion: String = "",
- val showNotAnOdinDialog: Boolean = false,
+ val showIncompatibleDeviceDialog: Boolean = false,
val showPServerNotAvailableDialog: Boolean = false,
- val singleHomeEnabled: Boolean = false,
+ val singlePressHomeEnabled: Boolean = false,
val appOverridesEnabled: Boolean = true,
val overrideDelayEnabled: Boolean = false,
@@ -30,6 +30,10 @@ data class MainUiModel(
val showRemapButtonDialog: Boolean = false,
val currentButtonSetting: String = "",
val currentButtonKeyCode: Int = 0,
+
+ val showChargeLimitDialog: Boolean = false,
+ val chargeLimitEnabled: Boolean = false,
+ val currentChargeLimit: ClosedRange = 20..80,
)
class CheckboxPreferenceUiModel(
diff --git a/app/src/main/java/de/langerhans/odintools/main/MainViewModel.kt b/app/src/main/java/de/langerhans/odintools/main/MainViewModel.kt
index 333b4f4..a6bc2aa 100644
--- a/app/src/main/java/de/langerhans/odintools/main/MainViewModel.kt
+++ b/app/src/main/java/de/langerhans/odintools/main/MainViewModel.kt
@@ -6,21 +6,30 @@ import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import de.langerhans.odintools.R
import de.langerhans.odintools.data.SharedPrefsRepo
-import de.langerhans.odintools.models.ControllerStyle.*
-import de.langerhans.odintools.models.L2R2Style.*
+import de.langerhans.odintools.models.ControllerStyle.Disconnect
+import de.langerhans.odintools.models.ControllerStyle.Odin
+import de.langerhans.odintools.models.ControllerStyle.Xbox
+import de.langerhans.odintools.models.L2R2Style.Analog
+import de.langerhans.odintools.models.L2R2Style.Both
+import de.langerhans.odintools.models.L2R2Style.Digital
import de.langerhans.odintools.tools.DeviceType.ODIN2
import de.langerhans.odintools.tools.DeviceUtils
+import de.langerhans.odintools.tools.SettingsRepo
import de.langerhans.odintools.tools.ShellExecutor
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
import javax.inject.Inject
@HiltViewModel
class MainViewModel @Inject constructor(
- deviceUtils: DeviceUtils,
+ private val deviceUtils: DeviceUtils,
private val executor: ShellExecutor,
+ private val settings: SettingsRepo,
private val prefs: SharedPrefsRepo,
) : ViewModel() {
@@ -36,39 +45,37 @@ class MainViewModel @Inject constructor(
get() = _l2r2StyleOptions
init {
- executor.applyRequiredSettings()
+ settings.applyRequiredSettings()
val deviceType = deviceUtils.getDeviceType()
- val preventHomePressSetting = executor.getBooleanSystemSetting("prevent_press_home_accidentally", true)
- val vibrationOnSetting = executor.getBooleanSystemSetting("vibrate_on", true)
- val vibrationStrength = executor.getVibrationStrength()
_uiState.update { _ ->
MainUiModel(
deviceType = deviceType,
deviceVersion = deviceUtils.getDeviceVersion(),
- showNotAnOdinDialog = deviceType != ODIN2,
- singleHomeEnabled = !preventHomePressSetting,
+ showIncompatibleDeviceDialog = deviceType != ODIN2,
+ singlePressHomeEnabled = !settings.preventPressHome,
showPServerNotAvailableDialog = !executor.pServerAvailable,
overrideDelayEnabled = prefs.overrideDelay,
- vibrationEnabled = vibrationOnSetting,
- currentVibration = vibrationStrength,
+ vibrationEnabled = settings.vibrationEnabled,
+ currentVibration = settings.vibrationStrength,
+ chargeLimitEnabled = prefs.chargeLimitEnabled,
)
}
}
fun incompatibleDeviceDialogDismissed() {
_uiState.update { current ->
- current.copy(showNotAnOdinDialog = false)
+ current.copy(showIncompatibleDeviceDialog = false)
}
}
- fun updateSingleHomePreference(newValue: Boolean) {
+ fun updateSinglePressHomePreference(newValue: Boolean) {
// Invert here as prevent == double press
- executor.setBooleanSystemSetting("prevent_press_home_accidentally", !newValue)
+ settings.preventPressHome = !newValue
_uiState.update { current ->
- current.copy(singleHomeEnabled = newValue)
+ current.copy(singlePressHomeEnabled = newValue)
}
}
@@ -100,14 +107,14 @@ class MainViewModel @Inject constructor(
fun showL2r2StylePreference() {
_l2r2StyleOptions = getCurrentL2r2Styles().toMutableStateList()
- _uiState.update { current ->
- current.copy(showL2r2StyleDialog = true)
+ _uiState.update {
+ it.copy(showL2r2StyleDialog = true)
}
}
fun hideL2r2StylePreference() {
- _uiState.update { current ->
- current.copy(showL2r2StyleDialog = false)
+ _uiState.update {
+ it.copy(showL2r2StyleDialog = false)
}
}
@@ -138,23 +145,22 @@ class MainViewModel @Inject constructor(
fun saveSaturation(newValue: Float) {
prefs.saturationOverride = newValue
- executor.setSfSaturation(newValue)
+ settings.setSfSaturation(newValue)
_uiState.update {
it.copy(showSaturationDialog = false)
}
}
fun updateVibrationPreference(newValue: Boolean) {
- executor.setBooleanSystemSetting("vibrate_on", newValue)
-
- _uiState.update { current ->
- current.copy(vibrationEnabled = newValue)
+ settings.vibrationEnabled = newValue
+ _uiState.update {
+ it.copy(vibrationEnabled = newValue)
}
}
fun vibrationClicked() {
_uiState.update {
- it.copy(showVibrationDialog = true, currentVibration = executor.getVibrationStrength())
+ it.copy(showVibrationDialog = true, currentVibration = settings.vibrationStrength)
}
}
@@ -166,7 +172,7 @@ class MainViewModel @Inject constructor(
fun saveVibration(newValue: Int) {
prefs.vibrationStrength = newValue
- executor.setVibrationStrength(newValue)
+ settings.vibrationStrength = newValue
_uiState.update {
it.copy(showVibrationDialog = false, currentVibration = newValue)
}
@@ -189,10 +195,10 @@ class MainViewModel @Inject constructor(
}
private fun getDefaultKeyCode(setting: String): Int {
- if (setting == "remap_custom_to_m1_value") {
+ if (setting == SettingsRepo.KEY_CUSTOM_M1_VALUE) {
return KeyEvent.KEYCODE_BUTTON_C
}
- if (setting == "remap_custom_to_m2_value") {
+ if (setting == SettingsRepo.KEY_CUSTOM_M2_VALUE) {
return KeyEvent.KEYCODE_BUTTON_Z
}
return KeyEvent.KEYCODE_UNKNOWN
@@ -226,4 +232,42 @@ class MainViewModel @Inject constructor(
it.copy(overrideDelayEnabled = newValue)
}
}
+
+ fun updateChargeLimitPreference(newValue: Boolean) {
+ prefs.chargeLimitEnabled = newValue
+ _uiState.update {
+ it.copy(chargeLimitEnabled = newValue)
+ }
+ }
+
+ fun chargeLimitClicked() {
+ _uiState.update {
+ it.copy(showChargeLimitDialog = true, currentChargeLimit = prefs.minBatteryLevel..prefs.maxBatteryLevel)
+ }
+ }
+
+ fun chargeLimitDialogDismissed() {
+ _uiState.update {
+ it.copy(showChargeLimitDialog = false)
+ }
+ }
+
+ fun saveChargeLimit(newValue: ClosedRange) {
+ prefs.minBatteryLevel = newValue.start
+ prefs.maxBatteryLevel = newValue.endInclusive
+
+ _uiState.update {
+ it.copy(showChargeLimitDialog = false, currentChargeLimit = newValue)
+ }
+ }
+
+ fun dumpLogToFile() {
+ val currentDate = Date()
+ val dateFormat = SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault())
+ val timeStamp = dateFormat.format(currentDate)
+ val directory = "/storage/emulated/0"
+ val fileName = "$directory/OdinTools_$timeStamp.log"
+ executor
+ .executeAsRoot("logcat -d -v threadtime > $fileName")
+ }
}
diff --git a/app/src/main/java/de/langerhans/odintools/models/PerfMode.kt b/app/src/main/java/de/langerhans/odintools/models/PerfMode.kt
index 5ee7106..1d75646 100644
--- a/app/src/main/java/de/langerhans/odintools/models/PerfMode.kt
+++ b/app/src/main/java/de/langerhans/odintools/models/PerfMode.kt
@@ -11,7 +11,7 @@ sealed class PerfMode(
) {
data object Standard : PerfMode("standard", 0, R.string.standard)
data object Performance : PerfMode("performance", 1, R.string.performance)
- data object HighPerformance : PerfMode("highPerformance", 2, R.string.highperformance)
+ data object HighPerformance : PerfMode("highPerformance", 2, R.string.highPerformance)
data object Unknown : PerfMode("unknown", -1, R.string.unknown)
fun enable(executor: ShellExecutor) {
diff --git a/app/src/main/java/de/langerhans/odintools/service/ForegroundAppWatcherService.kt b/app/src/main/java/de/langerhans/odintools/service/ForegroundAppWatcherService.kt
index 84b73b7..1225c3d 100644
--- a/app/src/main/java/de/langerhans/odintools/service/ForegroundAppWatcherService.kt
+++ b/app/src/main/java/de/langerhans/odintools/service/ForegroundAppWatcherService.kt
@@ -1,6 +1,7 @@
package de.langerhans.odintools.service
import android.accessibilityservice.AccessibilityService
+import android.content.IntentFilter
import android.database.ContentObserver
import android.os.Handler
import android.os.Looper
@@ -15,6 +16,7 @@ import de.langerhans.odintools.models.ControllerStyle.Unknown
import de.langerhans.odintools.models.FanMode
import de.langerhans.odintools.models.L2R2Style
import de.langerhans.odintools.models.PerfMode
+import de.langerhans.odintools.tools.BatteryLevelReceiver
import de.langerhans.odintools.tools.ShellExecutor
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -30,10 +32,12 @@ class ForegroundAppWatcherService @Inject constructor() : AccessibilityService()
lateinit var appOverrideDao: AppOverrideDao
@Inject
- lateinit var shellExecutor: ShellExecutor
+ lateinit var executor: ShellExecutor
@Inject
- lateinit var sharedPrefsRepo: SharedPrefsRepo
+ lateinit var prefs: SharedPrefsRepo
+
+ private var batteryLevelReceiver: BatteryLevelReceiver = BatteryLevelReceiver()
private val job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.IO + job)
@@ -52,12 +56,14 @@ class ForegroundAppWatcherService @Inject constructor() : AccessibilityService()
private var currentIme = ""
private val imeObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
override fun onChange(selfChange: Boolean) {
- currentIme = shellExecutor
+ currentIme = executor
.executeAsRoot("settings get secure default_input_method")
.getOrDefault("") ?: ""
}
}
+ private var chargeLimitEnabled: Boolean = false
+
override fun onAccessibilityEvent(event: AccessibilityEvent) {
if (shouldIgnore(event)) return
@@ -90,38 +96,38 @@ class ForegroundAppWatcherService @Inject constructor() : AccessibilityService()
private fun applyOverride(override: AppOverrideEntity) {
// This check makes sure that we don't override the "defaults" when switching between apps with overrides
if (!hasSetOverride) {
- savedControllerStyle = ControllerStyle.getStyle(shellExecutor)
- savedL2R2Style = L2R2Style.getStyle(shellExecutor)
- savedPerfMode = PerfMode.getMode(shellExecutor)
- savedFanMode = FanMode.getMode(shellExecutor)
+ savedControllerStyle = ControllerStyle.getStyle(executor)
+ savedL2R2Style = L2R2Style.getStyle(executor)
+ savedPerfMode = PerfMode.getMode(executor)
+ savedFanMode = FanMode.getMode(executor)
}
ControllerStyle.getById(override.controllerStyle).takeIf {
it != Unknown
- }?.enable(shellExecutor) ?: run {
+ }?.enable(executor) ?: run {
// Reset to default if we switch between override and NoChange app
- savedControllerStyle?.enable(shellExecutor)
+ savedControllerStyle?.enable(executor)
}
L2R2Style.getById(override.l2R2Style).takeIf {
it != L2R2Style.Unknown
- }?.enable(shellExecutor) ?: run {
+ }?.enable(executor) ?: run {
// Reset to default if we switch between override and NoChange app
- savedL2R2Style?.enable(shellExecutor)
+ savedL2R2Style?.enable(executor)
}
PerfMode.getById(override.perfMode).takeIf {
it != PerfMode.Unknown
- }?.enable(shellExecutor) ?: run {
+ }?.enable(executor) ?: run {
// Reset to default if we switch between override and NoChange app
- savedPerfMode?.enable(shellExecutor)
+ savedPerfMode?.enable(executor)
}
FanMode.getById(override.fanMode).takeIf {
it != FanMode.Unknown
- }?.enable(shellExecutor) ?: run {
+ }?.enable(executor) ?: run {
// Reset to default if we switch between override and NoChange app
- savedFanMode?.enable(shellExecutor)
+ savedFanMode?.enable(executor)
}
hasSetOverride = true
@@ -130,21 +136,35 @@ class ForegroundAppWatcherService @Inject constructor() : AccessibilityService()
private fun resetOverrides() {
if (!hasSetOverride) return
- savedControllerStyle?.enable(shellExecutor)
+ savedControllerStyle?.enable(executor)
savedControllerStyle = null
- savedL2R2Style?.enable(shellExecutor)
+ savedL2R2Style?.enable(executor)
savedL2R2Style = null
- savedPerfMode?.enable(shellExecutor)
+ savedPerfMode?.enable(executor)
savedPerfMode = null
- savedFanMode?.enable(shellExecutor)
+ savedFanMode?.enable(executor)
savedFanMode = null
hasSetOverride = false
}
+ private fun applyChargeLimit(newValue: Boolean) {
+ if (newValue && !chargeLimitEnabled) {
+ val intentFilter = IntentFilter().apply {
+ BatteryLevelReceiver.ALLOWED_INTENTS.forEach { action ->
+ addAction(action)
+ }
+ }
+ registerReceiver(batteryLevelReceiver, intentFilter)
+ } else if (!newValue && chargeLimitEnabled) {
+ unregisterReceiver(batteryLevelReceiver)
+ }
+ chargeLimitEnabled = newValue
+ }
+
override fun onInterrupt() {
// Nothing here
}
@@ -152,7 +172,7 @@ class ForegroundAppWatcherService @Inject constructor() : AccessibilityService()
override fun onServiceConnected() {
super.onServiceConnected()
- currentIme = shellExecutor
+ currentIme = executor
.executeAsRoot("settings get secure default_input_method")
.getOrDefault("") ?: ""
contentResolver.registerContentObserver(
@@ -161,12 +181,17 @@ class ForegroundAppWatcherService @Inject constructor() : AccessibilityService()
imeObserver,
)
- overridesEnabled = sharedPrefsRepo.appOverridesEnabled
- sharedPrefsRepo.observeAppOverrideEnabledState(
+ overridesEnabled = prefs.appOverridesEnabled
+ prefs.observeAppOverrideEnabledState(
{ overridesEnabled = it },
{ overridesDelay = it },
)
+ applyChargeLimit(prefs.chargeLimitEnabled)
+ prefs.observeChargeLimitEnabledState {
+ applyChargeLimit(it)
+ }
+
scope.launch {
appOverrideDao.getAll()
.flowOn(Dispatchers.IO)
@@ -180,7 +205,8 @@ class ForegroundAppWatcherService @Inject constructor() : AccessibilityService()
super.onDestroy()
job.cancel()
contentResolver.unregisterContentObserver(imeObserver)
- sharedPrefsRepo.removeAppOverrideEnabledObserver()
+ prefs.removeAppOverrideEnabledObserver()
+ prefs.removeChargeLimitEnabledObserver()
}
companion object {
diff --git a/app/src/main/java/de/langerhans/odintools/tiles/ControllerTileService.kt b/app/src/main/java/de/langerhans/odintools/tiles/ControllerTileService.kt
index 1efefa8..b272bf9 100644
--- a/app/src/main/java/de/langerhans/odintools/tiles/ControllerTileService.kt
+++ b/app/src/main/java/de/langerhans/odintools/tiles/ControllerTileService.kt
@@ -6,7 +6,10 @@ import dagger.hilt.android.AndroidEntryPoint
import de.langerhans.odintools.R
import de.langerhans.odintools.data.SharedPrefsRepo
import de.langerhans.odintools.models.ControllerStyle
-import de.langerhans.odintools.models.ControllerStyle.*
+import de.langerhans.odintools.models.ControllerStyle.Disconnect
+import de.langerhans.odintools.models.ControllerStyle.Odin
+import de.langerhans.odintools.models.ControllerStyle.Unknown
+import de.langerhans.odintools.models.ControllerStyle.Xbox
import de.langerhans.odintools.tools.ShellExecutor
import javax.inject.Inject
diff --git a/app/src/main/java/de/langerhans/odintools/tiles/L2R2TileService.kt b/app/src/main/java/de/langerhans/odintools/tiles/L2R2TileService.kt
index 3e3199d..bb5883c 100644
--- a/app/src/main/java/de/langerhans/odintools/tiles/L2R2TileService.kt
+++ b/app/src/main/java/de/langerhans/odintools/tiles/L2R2TileService.kt
@@ -6,7 +6,10 @@ import dagger.hilt.android.AndroidEntryPoint
import de.langerhans.odintools.R
import de.langerhans.odintools.data.SharedPrefsRepo
import de.langerhans.odintools.models.L2R2Style
-import de.langerhans.odintools.models.L2R2Style.*
+import de.langerhans.odintools.models.L2R2Style.Analog
+import de.langerhans.odintools.models.L2R2Style.Both
+import de.langerhans.odintools.models.L2R2Style.Digital
+import de.langerhans.odintools.models.L2R2Style.Unknown
import de.langerhans.odintools.tools.ShellExecutor
import javax.inject.Inject
diff --git a/app/src/main/java/de/langerhans/odintools/tools/BatteryLevelReceiver.kt b/app/src/main/java/de/langerhans/odintools/tools/BatteryLevelReceiver.kt
new file mode 100644
index 0000000..d40ab93
--- /dev/null
+++ b/app/src/main/java/de/langerhans/odintools/tools/BatteryLevelReceiver.kt
@@ -0,0 +1,49 @@
+package de.langerhans.odintools.tools
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Context.BATTERY_SERVICE
+import android.content.Intent
+import android.os.BatteryManager
+import dagger.hilt.android.AndroidEntryPoint
+import dagger.hilt.android.qualifiers.ApplicationContext
+import de.langerhans.odintools.data.SharedPrefsRepo
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class BatteryLevelReceiver : BroadcastReceiver() {
+
+ @ApplicationContext
+ lateinit var context: Context
+
+ @Inject
+ lateinit var settings: SettingsRepo
+
+ @Inject
+ lateinit var prefs: SharedPrefsRepo
+
+ override fun onReceive(context: Context, intent: Intent) {
+ if (!prefs.chargeLimitEnabled || intent.action !in ALLOWED_INTENTS) {
+ return
+ }
+
+ val batteryManager = context.getSystemService(BATTERY_SERVICE) as BatteryManager
+ val batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
+
+ if (batteryLevel >= prefs.maxBatteryLevel && !settings.chargingSeparationEnabled()) {
+ settings.enableChargingSeparation()
+ } else if (batteryLevel <= prefs.minBatteryLevel && settings.chargingSeparationEnabled()) {
+ settings.disableChargingSeparation()
+ }
+ }
+
+ companion object {
+ val ALLOWED_INTENTS = listOf(
+ Intent.ACTION_BATTERY_CHANGED,
+ Intent.ACTION_POWER_CONNECTED,
+ Intent.ACTION_POWER_DISCONNECTED,
+ Intent.ACTION_SCREEN_OFF,
+ Intent.ACTION_SCREEN_ON,
+ )
+ }
+}
diff --git a/app/src/main/java/de/langerhans/odintools/tools/BootReceiver.kt b/app/src/main/java/de/langerhans/odintools/tools/BootReceiver.kt
index 8dbe6fb..0872a25 100644
--- a/app/src/main/java/de/langerhans/odintools/tools/BootReceiver.kt
+++ b/app/src/main/java/de/langerhans/odintools/tools/BootReceiver.kt
@@ -11,23 +11,22 @@ import javax.inject.Inject
class BootReceiver : BroadcastReceiver() {
@Inject
- lateinit var sharedPrefsRepo: SharedPrefsRepo
+ lateinit var settings: SettingsRepo
@Inject
- lateinit var executor: ShellExecutor
+ lateinit var prefs: SharedPrefsRepo
override fun onReceive(context: Context, intent: Intent) {
if (intent.action != Intent.ACTION_BOOT_COMPLETED) {
return
}
-
- val saturation = sharedPrefsRepo.saturationOverride
+ val saturation = prefs.saturationOverride
if (saturation != 1.0f) {
- executor.setSfSaturation(saturation)
+ settings.setSfSaturation(saturation)
}
- val vibrationStrength = sharedPrefsRepo.vibrationStrength
+ val vibrationStrength = prefs.vibrationStrength
if (vibrationStrength != 0) {
- executor.setVibrationStrength(vibrationStrength)
+ settings.vibrationStrength = vibrationStrength
}
}
}
diff --git a/app/src/main/java/de/langerhans/odintools/tools/DeviceUtils.kt b/app/src/main/java/de/langerhans/odintools/tools/DeviceUtils.kt
index e61c07c..56cb247 100644
--- a/app/src/main/java/de/langerhans/odintools/tools/DeviceUtils.kt
+++ b/app/src/main/java/de/langerhans/odintools/tools/DeviceUtils.kt
@@ -1,23 +1,25 @@
package de.langerhans.odintools.tools
-import de.langerhans.odintools.tools.DeviceType.*
+import de.langerhans.odintools.tools.DeviceType.ODIN2
+import de.langerhans.odintools.tools.DeviceType.OTHER
+import de.langerhans.odintools.tools.DeviceType.RP4
import javax.inject.Inject
class DeviceUtils @Inject constructor(
- private val shellExecutor: ShellExecutor,
+ private val executor: ShellExecutor,
) {
+ fun getDeviceVersion() = executor.getStringProperty(SettingsRepo.KEY_BUILD_VERSION, "")
+
+ fun getDeviceCodename() = executor.getStringProperty(SettingsRepo.KEY_VENDOR_NAME, "")
+
fun getDeviceType(): DeviceType {
- val deviceName = shellExecutor.executeAsRoot("getprop ro.vendor.retro.name").getOrNull()
- return when (deviceName) {
+ return when (getDeviceCodename()) {
"Q9" -> ODIN2
"4.0", "4.0P" -> RP4
else -> OTHER
}
}
-
- fun getDeviceVersion() = shellExecutor.executeAsRoot("getprop ro.build.odin2.ota.version").map { it ?: "" }
- .getOrDefault("")
}
enum class DeviceType {
diff --git a/app/src/main/java/de/langerhans/odintools/tools/SettingsRepo.kt b/app/src/main/java/de/langerhans/odintools/tools/SettingsRepo.kt
new file mode 100644
index 0000000..af0a572
--- /dev/null
+++ b/app/src/main/java/de/langerhans/odintools/tools/SettingsRepo.kt
@@ -0,0 +1,118 @@
+package de.langerhans.odintools.tools
+
+import de.langerhans.odintools.BuildConfig
+import javax.inject.Inject
+
+class SettingsRepo @Inject constructor(
+ private val executor: ShellExecutor,
+) {
+
+ fun applyRequiredSettings() {
+ enableA11yService()
+ grantAllAppsPermission()
+ // Don't add to whitelist on debug builds, otherwise even Android Studio can't kill the app
+ if (!BuildConfig.DEBUG) {
+ addOdinToolsToWhitelist()
+ }
+ }
+
+ private fun enableA11yService() {
+ val currentServices =
+ executor.executeAsRoot("settings get secure $KEY_ACCESSIBILITY_SERVICES")
+ .map { it ?: "" }
+ .getOrDefault("")
+
+ if (currentServices.contains(PACKAGE)) return
+
+ executor.executeAsRoot(
+ "settings put secure $KEY_ACCESSIBILITY_SERVICES $PACKAGE/$PACKAGE.service.ForegroundAppWatcherService:$currentServices"
+ .trimEnd(':'),
+ )
+ }
+
+ private fun grantAllAppsPermission() {
+ executor.executeAsRoot("pm grant $PACKAGE android.permission.QUERY_ALL_PACKAGES")
+ }
+
+ private fun addOdinToolsToWhitelist() {
+ val currentWhitelist = whitelist
+ if (currentWhitelist.contains(PACKAGE)) return
+ val newWhitelist = "$PACKAGE,$currentWhitelist".trimEnd(',')
+ whitelist = newWhitelist
+ }
+
+ fun setSfSaturation(value: Float) {
+ executor.executeAsRoot("service call SurfaceFlinger 1022 f ${String.format("%.1f", value)}")
+ }
+
+ fun enableChargingSeparation() {
+ isChargingSeparation = true
+ restrictCurrent = 1000
+ restrictCharge = true
+ }
+
+ fun disableChargingSeparation() {
+ isChargingSeparation = false
+ restrictCurrent = 1000000
+ restrictCharge = false
+ }
+
+ fun chargingSeparationEnabled(): Boolean {
+ return isChargingSeparation && restrictCharge && restrictCurrent == 1000
+ }
+
+ private var whitelist: String
+ get() = executor.getStringSystemSetting(KEY_APP_WHITELIST, "")
+ set(value) = executor.setStringSystemSetting(KEY_APP_WHITELIST, value)
+
+ var preventPressHome: Boolean
+ get() = executor.getBooleanSystemSetting(KEY_PREVENT_PRESS_HOME, true)
+ set(value) = executor.setBooleanSystemSetting(KEY_PREVENT_PRESS_HOME, value)
+
+ var vibrationEnabled: Boolean
+ get() = executor.getBooleanSystemSetting(KEY_VIBRATE_ON, false)
+ set(value) = executor.setBooleanSystemSetting(KEY_VIBRATE_ON, value)
+
+ var vibrationStrength: Int
+ get() = executor.getIntValue(KEY_VIBRATION_STRENGTH, 0)
+ set(value) = executor.setIntValue(KEY_VIBRATION_STRENGTH, value)
+
+ var isChargingSeparation: Boolean
+ get() = executor.getBooleanSystemSetting(KEY_CHARGING_SEPARATION, false)
+ set(value) = executor.setBooleanSystemSetting(KEY_CHARGING_SEPARATION, value)
+
+ var restrictCharge: Boolean
+ get() = executor.getBooleanValue(KEY_RESTRICT_CHARGE, false)
+ set(value) = executor.setBooleanValue(KEY_RESTRICT_CHARGE, value)
+
+ var restrictCurrent: Int
+ get() = executor.getIntValue(KEY_RESTRICT_CURRENT, 0)
+ set(value) = executor.setIntValue(KEY_RESTRICT_CURRENT, value)
+
+ var chargingLimit80Enabled: Boolean
+ get() = executor.getBooleanSystemSetting(KEY_CHARGING_LIMIT_80, false)
+ set(value) = executor.setBooleanSystemSetting(KEY_CHARGING_LIMIT_80, value)
+
+ var chargingLimit10Enabled: Boolean
+ get() = executor.getBooleanSystemSetting(KEY_CHARGING_LIMIT_10, false)
+ set(value) = executor.setBooleanSystemSetting(KEY_CHARGING_LIMIT_10, value)
+
+ companion object {
+ private const val PACKAGE = BuildConfig.APPLICATION_ID
+ const val KEY_VENDOR_NAME = "ro.vendor.retro.name"
+ const val KEY_BUILD_VERSION = "ro.build.odin2.ota.version"
+ const val KEY_SATURATION = "persist.sys.sf.color_saturation"
+ const val KEY_ACCESSIBILITY_SERVICES = "enabled_accessibility_services"
+ const val KEY_APP_WHITELIST = "app_whiteList"
+ const val KEY_PREVENT_PRESS_HOME = "prevent_press_home_accidentally"
+ const val KEY_VIBRATE_ON = "vibrate_on"
+ const val KEY_CUSTOM_M1_VALUE = "remap_custom_to_m1_value"
+ const val KEY_CUSTOM_M2_VALUE = "remap_custom_to_m2_value"
+ const val KEY_VIBRATION_STRENGTH = "/d/haptics/user_vmax_mv"
+ const val KEY_CHARGING_SEPARATION = "is_charging_separation"
+ const val KEY_CHARGING_LIMIT_80 = "charging_limit_greater_than_80"
+ const val KEY_CHARGING_LIMIT_10 = "charging_limit_less_than_10"
+ const val KEY_RESTRICT_CHARGE = "/sys/class/qcom-battery/restrict_chg"
+ const val KEY_RESTRICT_CURRENT = "/sys/class/qcom-battery/restrict_cur"
+ }
+}
diff --git a/app/src/main/java/de/langerhans/odintools/tools/ShellExecutor.kt b/app/src/main/java/de/langerhans/odintools/tools/ShellExecutor.kt
index 9ebf3bc..df8d489 100644
--- a/app/src/main/java/de/langerhans/odintools/tools/ShellExecutor.kt
+++ b/app/src/main/java/de/langerhans/odintools/tools/ShellExecutor.kt
@@ -3,7 +3,6 @@ package de.langerhans.odintools.tools
import android.annotation.SuppressLint
import android.os.IBinder
import android.os.Parcel
-import de.langerhans.odintools.BuildConfig
import java.nio.charset.Charset
import javax.inject.Inject
@@ -42,88 +41,109 @@ class ShellExecutor @Inject constructor() {
return Result.success(result)
}
- fun getIntSystemSetting(setting: String, defaultValue: Int): Int {
- return executeAsRoot("settings get system $setting")
+ private fun getProperty(property: String): Result {
+ return executeAsRoot("getprop $property")
+ }
+
+ fun getIntProperty(property: String, defaultValue: Int): Int {
+ return getProperty(property)
.mapCatching { it?.toInt() ?: defaultValue }
.getOrDefault(defaultValue)
}
- fun setIntSystemSetting(setting: String, value: Int) {
- executeAsRoot("settings put system $setting $value")
+ fun getFloatProperty(property: String, defaultValue: Float): Float {
+ return getProperty(property)
+ .mapCatching { it?.toFloat() ?: defaultValue }
+ .getOrDefault(defaultValue)
}
- fun getBooleanSystemSetting(setting: String, defaultValue: Boolean): Boolean {
- return executeAsRoot("settings get system $setting")
+ fun getBooleanProperty(property: String, defaultValue: Boolean): Boolean {
+ return getProperty(property)
.map { if (it == null) defaultValue else it == "1" }
.getOrDefault(defaultValue)
}
- fun setBooleanSystemSetting(setting: String, value: Boolean) {
- setIntSystemSetting(setting, if (value) 1 else 0)
+ fun getStringProperty(property: String, defaultValue: String): String {
+ return getProperty(property)
+ .map { it ?: defaultValue }
+ .getOrDefault(defaultValue)
+ }
+
+ private fun getSystemSetting(setting: String): Result {
+ return executeAsRoot("settings get system $setting")
}
fun getStringSystemSetting(setting: String, defaultValue: String): String {
- return executeAsRoot("settings get system $setting").map {
- it ?: defaultValue
- }.getOrDefault(defaultValue)
+ return getSystemSetting(setting)
+ .map { it ?: defaultValue }
+ .getOrDefault(defaultValue)
}
fun setStringSystemSetting(setting: String, value: String) {
executeAsRoot("settings put system $setting $value")
}
- private fun enableA11yService() {
- val currentServices =
- executeAsRoot("settings get secure enabled_accessibility_services")
- .map { it ?: "" }
- .getOrDefault("")
+ fun getIntSystemSetting(setting: String, defaultValue: Int): Int {
+ return getSystemSetting(setting)
+ .mapCatching { it?.toInt() ?: defaultValue }
+ .getOrDefault(defaultValue)
+ }
+
+ fun setIntSystemSetting(setting: String, value: Int) {
+ executeAsRoot("settings put system $setting $value")
+ }
- if (currentServices.contains(PACKAGE)) return
+ fun getBooleanSystemSetting(setting: String, defaultValue: Boolean): Boolean {
+ return getSystemSetting(setting)
+ .map { if (it == null) defaultValue else it == "1" }
+ .getOrDefault(defaultValue)
+ }
- executeAsRoot(
- "settings put secure enabled_accessibility_services $PACKAGE/$PACKAGE.service.ForegroundAppWatcherService:$currentServices"
- .trimEnd(':'),
- )
+ fun setBooleanSystemSetting(setting: String, value: Boolean) {
+ setIntSystemSetting(setting, if (value) 1 else 0)
}
- fun setSfSaturation(saturation: Float) {
- executeAsRoot("service call SurfaceFlinger 1022 f ${String.format("%.1f", saturation)}")
+ private fun getValue(file: String): Result {
+ return executeAsRoot("cat $file")
}
- fun setVibrationStrength(newValue: Int) {
- executeAsRoot("echo $newValue > /d/haptics/user_vmax_mv")
+ fun getStringValue(file: String, defaultValue: String): String {
+ return getValue(file)
+ .map { it ?: defaultValue }
+ .getOrDefault(defaultValue)
}
- fun getVibrationStrength(): Int {
- val defaultValue = 0
+ fun setStringValue(file: String, value: String) {
+ executeAsRoot("echo $value > $file")
+ }
- return executeAsRoot("cat /d/haptics/user_vmax_mv")
- .mapCatching { it?.toInt() ?: defaultValue } // This throws on RP4 because it has no rumble
+ fun getIntValue(file: String, defaultValue: Int): Int {
+ return getValue(file)
+ .mapCatching { it?.toInt() ?: defaultValue }
.getOrDefault(defaultValue)
}
- private fun grantAllAppsPermission() {
- executeAsRoot("pm grant $PACKAGE android.permission.QUERY_ALL_PACKAGES")
+ fun setIntValue(file: String, value: Int) {
+ executeAsRoot("echo $value > $file")
}
- private fun addOdinToolsToWhitelist() {
- val currentWhitelist = getStringSystemSetting("app_whiteList", "")
- if (currentWhitelist.contains(PACKAGE)) return
+ fun getFloatValue(file: String, defaultValue: Float): Float {
+ return getValue(file)
+ .mapCatching { it?.toFloat() ?: defaultValue }
+ .getOrDefault(defaultValue)
+ }
- val newWhitelist = "$PACKAGE,$currentWhitelist".trimEnd(',')
- setStringSystemSetting("app_whiteList", newWhitelist)
+ fun setFloatValue(file: String, value: Float) {
+ executeAsRoot("echo $value > $file")
}
- fun applyRequiredSettings() {
- enableA11yService()
- grantAllAppsPermission()
- if (!BuildConfig.DEBUG) {
- // Don't add to whitelist on debug builds, otherwise even Android Studio can't kill the app
- addOdinToolsToWhitelist()
- }
+ fun getBooleanValue(file: String, defaultValue: Boolean): Boolean {
+ return getValue(file)
+ .map { if (it == null) defaultValue else it == "1" }
+ .getOrDefault(defaultValue)
}
- companion object {
- private const val PACKAGE = BuildConfig.APPLICATION_ID
+ fun setBooleanValue(file: String, value: Boolean) {
+ setIntValue(file, if (value) 1 else 0)
}
}
diff --git a/app/src/main/java/de/langerhans/odintools/ui/composables/LargeDropDownMenu.kt b/app/src/main/java/de/langerhans/odintools/ui/composables/LargeDropDownMenu.kt
index e474971..5a05d63 100644
--- a/app/src/main/java/de/langerhans/odintools/ui/composables/LargeDropDownMenu.kt
+++ b/app/src/main/java/de/langerhans/odintools/ui/composables/LargeDropDownMenu.kt
@@ -1,7 +1,13 @@
package de.langerhans.odintools.ui.composables
import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.IntrinsicSize
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
@@ -9,8 +15,20 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp
-import androidx.compose.material3.*
-import androidx.compose.runtime.*
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Icon
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
diff --git a/app/src/main/java/de/langerhans/odintools/ui/composables/Preferences.kt b/app/src/main/java/de/langerhans/odintools/ui/composables/Preferences.kt
index 047493d..33842a9 100644
--- a/app/src/main/java/de/langerhans/odintools/ui/composables/Preferences.kt
+++ b/app/src/main/java/de/langerhans/odintools/ui/composables/Preferences.kt
@@ -7,11 +7,37 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.IntrinsicSize
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
-import androidx.compose.material3.*
-import androidx.compose.runtime.*
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Card
+import androidx.compose.material3.Checkbox
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.RangeSlider
+import androidx.compose.material3.Slider
+import androidx.compose.material3.Switch
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.material3.VerticalDivider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+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
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
@@ -25,6 +51,8 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import de.langerhans.odintools.R
import de.langerhans.odintools.main.CheckboxPreferenceUiModel
+import de.langerhans.odintools.ui.theme.Typography
+import kotlin.math.roundToInt
@Composable
fun SettingsHeader(@StringRes name: Int) {
@@ -150,6 +178,7 @@ fun TriggerPreference(@DrawableRes icon: Int, @StringRes title: Int, @StringRes
@Composable
fun CheckBoxDialogPreference(
items: List,
+ @StringRes title: Int,
minSelected: Int = 2,
onCancel: () -> Unit,
onSave: (items: List) -> Unit,
@@ -161,14 +190,13 @@ fun CheckBoxDialogPreference(
}, dismissButton = {
DialogButton(text = stringResource(id = R.string.cancel), onCancel)
}, title = {
- Text(text = stringResource(id = R.string.controllerStyle))
+ Text(text = stringResource(id = title))
}, text = {
LazyColumn {
items(items = items, key = { item -> item.key }) { item ->
fun canChangeCheckbox(): Boolean {
return item.checked.not() || items.count { it.checked } == minSelected + 1
}
-
CheckboxDialogRow(
text = stringResource(id = item.text),
checked = item.checked,
@@ -312,7 +340,8 @@ fun RemapButtonDialog(initialValue: Int, onCancel: () -> Unit, onReset: () -> Un
)
Row(
modifier = Modifier
- .fillMaxWidth(),
+ .fillMaxWidth()
+ .padding(top = 24.dp),
horizontalArrangement = Arrangement.SpaceBetween,
) {
TextButton(onClick = onReset) {
@@ -335,3 +364,56 @@ fun RemapButtonDialog(initialValue: Int, onCancel: () -> Unit, onReset: () -> Un
focusRequester.requestFocus()
}
}
+
+@Composable
+fun ChargeLimitPreferenceDialog(initialValue: ClosedRange, onCancel: () -> Unit, onSave: (newValue: ClosedRange) -> Unit) {
+ var userValue by remember {
+ mutableStateOf(initialValue.start.toFloat()..initialValue.endInclusive.toFloat())
+ }
+ val start = userValue.start.roundToInt()
+ val end = userValue.endInclusive.roundToInt()
+
+ AlertDialog(
+ onDismissRequest = {},
+ confirmButton = {
+ DialogButton(text = stringResource(id = R.string.save)) {
+ onSave(start..end)
+ }
+ },
+ dismissButton = {
+ DialogButton(text = stringResource(id = R.string.cancel), onCancel)
+ },
+ title = {
+ Text(text = stringResource(id = R.string.chargeLimit))
+ },
+ text = {
+ Column {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ RangeSlider(
+ value = userValue,
+ valueRange = 0f..100f,
+ steps = 9,
+ onValueChange = {
+ userValue = it
+ },
+ modifier = Modifier
+ .weight(1f),
+ )
+ }
+ Row {
+ Text(text = stringResource(id = R.string.chargeLimitPreferenceDialogOffAt, start))
+ Spacer(modifier = Modifier.weight(1f))
+ Text(text = stringResource(id = R.string.chargeLimitPreferenceDialogOnAt, end))
+ }
+ Row {
+ Text(
+ style = Typography.labelSmall,
+ text = stringResource(id = R.string.chargeLimitPreferenceDialogDescription),
+ )
+ }
+ }
+ },
+ )
+}
diff --git a/app/src/main/res/drawable-night/ic_add.xml b/app/src/main/res/drawable-night/ic_add.xml
new file mode 100644
index 0000000..c2c3eb3
--- /dev/null
+++ b/app/src/main/res/drawable-night/ic_add.xml
@@ -0,0 +1,9 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-night/ic_app_settings.xml b/app/src/main/res/drawable-night/ic_app_settings.xml
index e487ecb..1a7eac7 100644
--- a/app/src/main/res/drawable-night/ic_app_settings.xml
+++ b/app/src/main/res/drawable-night/ic_app_settings.xml
@@ -3,7 +3,7 @@
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-night/ic_controllerstyle.xml b/app/src/main/res/drawable-night/ic_controllerstyle.xml
deleted file mode 100644
index 1bee04c..0000000
--- a/app/src/main/res/drawable-night/ic_controllerstyle.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable-night/ic_electrical_services.xml b/app/src/main/res/drawable-night/ic_electrical_services.xml
new file mode 100644
index 0000000..c326ea5
--- /dev/null
+++ b/app/src/main/res/drawable-night/ic_electrical_services.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-night/ic_face_buttons.xml b/app/src/main/res/drawable-night/ic_face_buttons.xml
new file mode 100644
index 0000000..dcdae7c
--- /dev/null
+++ b/app/src/main/res/drawable-night/ic_face_buttons.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-night/ic_file_save.xml b/app/src/main/res/drawable-night/ic_file_save.xml
new file mode 100644
index 0000000..89ca44b
--- /dev/null
+++ b/app/src/main/res/drawable-night/ic_file_save.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable-night/ic_gamepad.xml b/app/src/main/res/drawable-night/ic_gamepad.xml
index dca33ef..d0a69dc 100644
--- a/app/src/main/res/drawable-night/ic_gamepad.xml
+++ b/app/src/main/res/drawable-night/ic_gamepad.xml
@@ -1,5 +1,9 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-night/ic_home.xml b/app/src/main/res/drawable-night/ic_home.xml
index c66f285..3d118d2 100644
--- a/app/src/main/res/drawable-night/ic_home.xml
+++ b/app/src/main/res/drawable-night/ic_home.xml
@@ -3,7 +3,7 @@
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-night/ic_info.xml b/app/src/main/res/drawable-night/ic_info.xml
index 1ece034..a9235f6 100644
--- a/app/src/main/res/drawable-night/ic_info.xml
+++ b/app/src/main/res/drawable-night/ic_info.xml
@@ -3,7 +3,7 @@
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-night/ic_more_time.xml b/app/src/main/res/drawable-night/ic_more_time.xml
index 7d150d0..02f065f 100644
--- a/app/src/main/res/drawable-night/ic_more_time.xml
+++ b/app/src/main/res/drawable-night/ic_more_time.xml
@@ -3,7 +3,7 @@
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-night/ic_palette.xml b/app/src/main/res/drawable-night/ic_palette.xml
index cb4385b..853ee77 100644
--- a/app/src/main/res/drawable-night/ic_palette.xml
+++ b/app/src/main/res/drawable-night/ic_palette.xml
@@ -3,7 +3,7 @@
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-night/ic_sliders.xml b/app/src/main/res/drawable-night/ic_sliders.xml
index eaa85cc..e916d26 100644
--- a/app/src/main/res/drawable-night/ic_sliders.xml
+++ b/app/src/main/res/drawable-night/ic_sliders.xml
@@ -3,7 +3,7 @@
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml
index 760187a..1e43f8b 100644
--- a/app/src/main/res/drawable/ic_add.xml
+++ b/app/src/main/res/drawable/ic_add.xml
@@ -3,7 +3,7 @@
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_app_settings.xml b/app/src/main/res/drawable/ic_app_settings.xml
index ad608b9..328bc54 100644
--- a/app/src/main/res/drawable/ic_app_settings.xml
+++ b/app/src/main/res/drawable/ic_app_settings.xml
@@ -3,7 +3,7 @@
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_controllerstyle.xml b/app/src/main/res/drawable/ic_controllerstyle.xml
deleted file mode 100644
index 3eb98b9..0000000
--- a/app/src/main/res/drawable/ic_controllerstyle.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_electrical_services.xml b/app/src/main/res/drawable/ic_electrical_services.xml
new file mode 100644
index 0000000..a6a752f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_electrical_services.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_face_buttons.xml b/app/src/main/res/drawable/ic_face_buttons.xml
new file mode 100644
index 0000000..e4abe3e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_face_buttons.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_file_save.xml b/app/src/main/res/drawable/ic_file_save.xml
new file mode 100644
index 0000000..d5383d2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_file_save.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_gamepad.xml b/app/src/main/res/drawable/ic_gamepad.xml
index f3e4143..82ce9f9 100644
--- a/app/src/main/res/drawable/ic_gamepad.xml
+++ b/app/src/main/res/drawable/ic_gamepad.xml
@@ -1,5 +1,8 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_home.xml b/app/src/main/res/drawable/ic_home.xml
index e45e971..33a4b84 100644
--- a/app/src/main/res/drawable/ic_home.xml
+++ b/app/src/main/res/drawable/ic_home.xml
@@ -3,7 +3,7 @@
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_info.xml b/app/src/main/res/drawable/ic_info.xml
index 2257ddf..db588db 100644
--- a/app/src/main/res/drawable/ic_info.xml
+++ b/app/src/main/res/drawable/ic_info.xml
@@ -3,7 +3,7 @@
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_more_time.xml b/app/src/main/res/drawable/ic_more_time.xml
index 720de56..22fae63 100644
--- a/app/src/main/res/drawable/ic_more_time.xml
+++ b/app/src/main/res/drawable/ic_more_time.xml
@@ -3,7 +3,7 @@
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_palette.xml b/app/src/main/res/drawable/ic_palette.xml
index a48e3c2..23f3974 100644
--- a/app/src/main/res/drawable/ic_palette.xml
+++ b/app/src/main/res/drawable/ic_palette.xml
@@ -3,7 +3,7 @@
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_sliders.xml b/app/src/main/res/drawable/ic_sliders.xml
index a8aa26b..1f21b16 100644
--- a/app/src/main/res/drawable/ic_sliders.xml
+++ b/app/src/main/res/drawable/ic_sliders.xml
@@ -3,7 +3,7 @@
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 573ff5c..b27fe68 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -3,16 +3,16 @@
Xbox
Odin
Disconnect
- Controller Style
+ Controller style
Unknown
- L2/R2 Mode
+ L2/R2 mode
Analog
Digital
Both
Performance Mode
Standard
Performance
- High Performance
+ High Performance
Fan Mode
Off
Quiet
@@ -20,13 +20,13 @@
Sport
Device version:\nApp version:
- Quick Settings
- Select which modes to cycle through in the Controller Style quick settings tile
- Select which modes to cycle through in the L2/R2 Mode quick settings tile
+ Quick Settings
+ Select which modes to cycle through in the Controller Style quick settings tile.
+ Select which modes to cycle through in the L2/R2 Mode quick settings tile.
Buttons
- Single press home button
- When enabled allows using the home button with a single press instead of a double press
+ Single-press home button
+ When enabled allows using the home button with a single press instead of a double press.
Warning
This app was made for the Ayn Odin 2. Running it on an incompatible device may cause damage. The author of this app takes no responsibility for any damage caused!
@@ -42,7 +42,7 @@
Display
Change the saturation of the display. Will be re-applied on reboot. Default value is 1.0.
- Used by OdinTools to detect running apps for dynamic switching of button settings
+ Used by OdinTools to detect running apps for dynamic switching of button settings.
App overrides
Set up overrides per app. Switch off to globally disable app overrides.
@@ -53,7 +53,7 @@
You already added overrides for all your apps
Fan mode is required when setting a Performance mode
Override delay
- Delay applying overrides by 0.5s. This can help if apps crash when applying an
+ Delay applying overrides by 0.5s. This can help if apps crash when applying an
override.
@@ -67,4 +67,17 @@
M2 button
Press any button
Set Default
-
\ No newline at end of file
+
+ Battery
+ Charge limit
+ When enabled activates charging separation at the specified battery levels.
+ Off at %1$d%%
+ On at %1$d%%
+
+ Charging separation means the power goes straight to the device, bypassing the battery.
+ Notice that if the power source is insufficient, battery drain will occur.
+
+ Debug
+ Dump log to file
+ Dump log output to a text file in the internal memory.
+
diff --git a/gradle.properties b/gradle.properties
index ada445c..a2a7978 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -29,3 +29,4 @@ android.enableBuildConfigAsBytecode=true
systemProp.javax.xml.parsers.SAXParserFactory=com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl
systemProp.javax.xml.transform.TransformerFactory=com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl
systemProp.javax.xml.parsers.DocumentBuilderFactory=com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl
+org.gradle.configuration-cache=true
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 3f49dee..d699884 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,17 +1,17 @@
[versions]
-androidx-activity = "1.8.2"
+androidx-activity = "1.9.0"
androidx-annotation-annotation-jvm = "1.7.1"
androidx-compose-bom = "2024.02.00"
-androidx-hilt = "1.1.0"
+androidx-hilt = "1.2.0"
androidx-lifecycle = "2.7.0"
androidx-navigation = "2.7.7"
androidx-room = "2.6.1"
androidx-test-espresso-espresso-core = "3.5.1"
androidx-test-ext-junit = "1.1.5"
-com-android-application = "8.2.2"
+com-android-application = "8.3.2"
com-github-ben-manes-versions = "0.51.0"
com-google-accompanist-accompanist-drawablepainter = "0.34.0"
-com-google-dagger = "2.50"
+com-google-dagger = "2.51.1"
com-google-dagger-hilt-android = "2.50"
com-google-devtools-ksp = "1.9.22-1.0.17"
junit = "4.13.2"