Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SettingsRepo, refactoring & setting for battery limit #28

Merged
merged 34 commits into from
May 15, 2024
Merged
Changes from 1 commit
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
9cdc836
Standardize formatting and color values of icons
miguelguzmanr Feb 14, 2024
2ca10a8
Merge remote-tracking branch 'upstream/main' into charging-separation
miguelguzmanr Feb 14, 2024
8474896
Rename face buttons
miguelguzmanr Feb 15, 2024
36f4b41
Allow title in CheckBoxDialogPreference
miguelguzmanr Feb 15, 2024
890cf4f
Attempt consistency in titles & descriptions
miguelguzmanr Feb 15, 2024
b8e7196
Add SettingsRepo & refactoring
miguelguzmanr Feb 15, 2024
dec5050
Add padding in RemapButtonDialog
miguelguzmanr Feb 15, 2024
390b5c6
Add setting to bypass charge after 80%
miguelguzmanr Feb 15, 2024
e048cb7
Merge pull request #1 from miguelguzmanr/charging-separation
miguelguzmanr Feb 15, 2024
c00bd51
Rename function getDeviceCodename
miguelguzmanr Feb 20, 2024
0e8b1b7
Remove debug println
miguelguzmanr Feb 20, 2024
37ef775
Add explicit function for Settings initialization
miguelguzmanr Feb 20, 2024
18bc583
Add constant for accessibility services setting
miguelguzmanr Feb 20, 2024
ad7bc88
Refactor saturation property to a function
miguelguzmanr Feb 20, 2024
8150976
Remove unused properties
miguelguzmanr Feb 20, 2024
75a492d
Merge branch 'main' into main
miguelguzmanr Apr 18, 2024
9c4521c
Optimize imports
miguelguzmanr Apr 21, 2024
f465920
Update gradle.properties
miguelguzmanr Apr 21, 2024
1860873
Update dependencies
miguelguzmanr Apr 21, 2024
f13a20a
Add setting for charging separation
miguelguzmanr Apr 21, 2024
d7f98ea
Fix typo in preference key
miguelguzmanr Apr 21, 2024
4c35532
Create .gitattributes
miguelguzmanr Apr 21, 2024
26094cb
Add battery level broadcast receiver
miguelguzmanr Apr 21, 2024
9844999
Remove unused preference
miguelguzmanr Apr 21, 2024
cc32a96
Enable Aggregating Task
miguelguzmanr Apr 27, 2024
a980356
Make context private in SharedPrefsRepo
miguelguzmanr Apr 27, 2024
d91acac
Make deviceUtils private in MainViewModel
miguelguzmanr Apr 27, 2024
d9a5af6
Add setting to dump a log file
miguelguzmanr Apr 28, 2024
3a3548d
Fix incorrect setting restrictCharge
miguelguzmanr Apr 28, 2024
46a6c57
Add battery level monitor service
miguelguzmanr Apr 28, 2024
884c8dc
Remove debug requirement for dump log to file
miguelguzmanr May 15, 2024
9b1e42f
Add Debug settings header
miguelguzmanr May 15, 2024
b62e33c
Add get & set float values in ShellExecutor
miguelguzmanr May 15, 2024
8d99eea
Migrate battery level monitor to accessibility service
miguelguzmanr May 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add SettingsRepo & refactoring
- Add a class for system settings.
- Attempt consistency in function & variable names.
  • Loading branch information
miguelguzmanr committed Feb 15, 2024
commit b8e7196bd71972ea4bec9d009ba0eeb02b8a73cf
11 changes: 6 additions & 5 deletions app/src/main/java/de/langerhans/odintools/main/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@ 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.tools.SettingsRepo
import de.langerhans.odintools.ui.composables.*
import de.langerhans.odintools.ui.theme.OdinToolsTheme

@@ -62,7 +63,7 @@ fun SettingsScreen(viewModel: MainViewModel = hiltViewModel(), navigateToOverrid

if (uiState.showPServerNotAvailableDialog) {
PServerNotAvailableDialog()
} else if (uiState.showNotAnOdinDialog) {
} else if (uiState.showIncompatibleDeviceDialog) {
NotAnOdinDialog { viewModel.incompatibleDeviceDialogDismissed() }
}

@@ -160,26 +161,26 @@ fun SettingsScreen(viewModel: MainViewModel = hiltViewModel(), navigateToOverrid
SettingsHeader(R.string.buttons)
SwitchPreference(
icon = R.drawable.ic_home,
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(
icon = R.drawable.ic_gamepad,
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)
6 changes: 4 additions & 2 deletions app/src/main/java/de/langerhans/odintools/main/MainUiModel.kt
Original file line number Diff line number Diff line change
@@ -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,8 @@ data class MainUiModel(
val showRemapButtonDialog: Boolean = false,
val currentButtonSetting: String = "",
val currentButtonKeyCode: Int = 0,

val chargeLimitEnabled: Boolean = false,
)

class CheckboxPreferenceUiModel(
45 changes: 25 additions & 20 deletions app/src/main/java/de/langerhans/odintools/main/MainViewModel.kt
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ import de.langerhans.odintools.models.ControllerStyle.*
import de.langerhans.odintools.models.L2R2Style.*
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
@@ -21,6 +22,7 @@ import javax.inject.Inject
class MainViewModel @Inject constructor(
deviceUtils: DeviceUtils,
private val executor: ShellExecutor,
private val settings: SettingsRepo,
private val prefs: SharedPrefsRepo,
) : ViewModel() {

@@ -36,39 +38,35 @@ class MainViewModel @Inject constructor(
get() = _l2r2StyleOptions

init {
executor.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 = settings.chargingLimit80Enabled,
)
}
}

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)
}
}

@@ -138,23 +136,22 @@ class MainViewModel @Inject constructor(

fun saveSaturation(newValue: Float) {
prefs.saturationOverride = newValue
executor.setSfSaturation(newValue)
settings.saturation = newValue
_uiState.update {
it.copy(showSaturationDialog = false)
}
}

fun updateVibrationPreference(newValue: Boolean) {
executor.setBooleanSystemSetting("vibrate_on", newValue)

settings.vibrationEnabled = newValue
_uiState.update { current ->
current.copy(vibrationEnabled = newValue)
}
}

fun vibrationClicked() {
_uiState.update {
it.copy(showVibrationDialog = true, currentVibration = executor.getVibrationStrength())
it.copy(showVibrationDialog = true, currentVibration = settings.vibrationStrength)
}
}

@@ -166,7 +163,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 +186,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 +223,12 @@ class MainViewModel @Inject constructor(
it.copy(overrideDelayEnabled = newValue)
}
}

fun updateChargeLimitPreference(newValue: Boolean) {
settings.chargingLimit80Enabled = newValue

_uiState.update { current ->
current.copy(chargeLimitEnabled = newValue)
}
}
}
12 changes: 6 additions & 6 deletions app/src/main/java/de/langerhans/odintools/tools/BootReceiver.kt
Original file line number Diff line number Diff line change
@@ -11,23 +11,23 @@ import javax.inject.Inject
class BootReceiver : BroadcastReceiver() {

@Inject
lateinit var sharedPrefsRepo: SharedPrefsRepo
lateinit var prefs: SharedPrefsRepo

@Inject
lateinit var executor: ShellExecutor
lateinit var settings: SettingsRepo

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.saturation = saturation
}
val vibrationStrength = sharedPrefsRepo.vibrationStrength
val vibrationStrength = prefs.vibrationStrength
if (vibrationStrength != 0) {
executor.setVibrationStrength(vibrationStrength)
settings.vibrationStrength = vibrationStrength
}
}
}
12 changes: 6 additions & 6 deletions app/src/main/java/de/langerhans/odintools/tools/DeviceUtils.kt
Original file line number Diff line number Diff line change
@@ -4,20 +4,20 @@ import de.langerhans.odintools.tools.DeviceType.*
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 getDeviceVendor() = executor.getStringProperty(SettingsRepo.KEY_VENDOR_NAME, "")
miguelguzmanr marked this conversation as resolved.
Show resolved Hide resolved

fun getDeviceType(): DeviceType {
val deviceName = shellExecutor.executeAsRoot("getprop ro.vendor.retro.name").getOrNull()
return when (deviceName) {
return when (getDeviceVendor()) {
"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 {
110 changes: 110 additions & 0 deletions app/src/main/java/de/langerhans/odintools/tools/SettingsRepo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package de.langerhans.odintools.tools

import de.langerhans.odintools.BuildConfig
import javax.inject.Inject

class SettingsRepo @Inject constructor(
private val executor: ShellExecutor,
) {

init {
miguelguzmanr marked this conversation as resolved.
Show resolved Hide resolved
println("Settings init")
miguelguzmanr marked this conversation as resolved.
Show resolved Hide resolved
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 enabled_accessibility_services")
miguelguzmanr marked this conversation as resolved.
Show resolved Hide resolved
.map { it ?: "" }
.getOrDefault("")

if (currentServices.contains(PACKAGE)) return

executor.executeAsRoot(
"settings put secure enabled_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
}

val buildVersion: String
get() = executor.getStringProperty(KEY_BUILD_VERSION, "")

val vendorName: String
get() = executor.getStringProperty(KEY_VENDOR_NAME, "")

var saturation: Float
get() = throw UnsupportedOperationException()
miguelguzmanr marked this conversation as resolved.
Show resolved Hide resolved
set(value) {
executor.executeAsRoot("service call SurfaceFlinger 1022 f ${String.format("%.1f", value)}")
}

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 chargingSeparationEnabled: Boolean
get() = executor.getBooleanSystemSetting(KEY_CHARGING_SEPARATION, false)
set(value) = executor.setBooleanSystemSetting(KEY_CHARGING_SEPARATION, value)

var restrictCharge: Boolean
get() = executor.getBooleanSystemSetting(KEY_RESTRICT_CHARGE, false)
set(value) = executor.setBooleanSystemSetting(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_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"
}
}
104 changes: 57 additions & 47 deletions app/src/main/java/de/langerhans/odintools/tools/ShellExecutor.kt
Original file line number Diff line number Diff line change
@@ -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,99 @@ 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<String?> {
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<String?> {
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("")

if (currentServices.contains(PACKAGE)) return
fun getIntSystemSetting(setting: String, defaultValue: Int): Int {
return getSystemSetting(setting)
.mapCatching { it?.toInt() ?: defaultValue }
.getOrDefault(defaultValue)
}

executeAsRoot(
"settings put secure enabled_accessibility_services $PACKAGE/$PACKAGE.service.ForegroundAppWatcherService:$currentServices"
.trimEnd(':'),
)
fun setIntSystemSetting(setting: String, value: Int) {
executeAsRoot("settings put system $setting $value")
}

fun setSfSaturation(saturation: Float) {
executeAsRoot("service call SurfaceFlinger 1022 f ${String.format("%.1f", saturation)}")
fun getBooleanSystemSetting(setting: String, defaultValue: Boolean): Boolean {
return getSystemSetting(setting)
.map { if (it == null) defaultValue else it == "1" }
.getOrDefault(defaultValue)
}

fun setVibrationStrength(newValue: Int) {
executeAsRoot("echo $newValue > /d/haptics/user_vmax_mv")
fun setBooleanSystemSetting(setting: String, value: Boolean) {
setIntSystemSetting(setting, if (value) 1 else 0)
}

fun getVibrationStrength(): Int {
val defaultValue = 0
private fun getValue(file: String): Result<String?> {
return executeAsRoot("cat $file")
}

return executeAsRoot("cat /d/haptics/user_vmax_mv")
.mapCatching { it?.toInt() ?: defaultValue } // This throws on RP4 because it has no rumble
fun getStringValue(file: String, defaultValue: String): String {
return getValue(file)
.map { it ?: defaultValue }
.getOrDefault(defaultValue)
}

private fun grantAllAppsPermission() {
executeAsRoot("pm grant $PACKAGE android.permission.QUERY_ALL_PACKAGES")
fun setStringValue(file: String, value: String) {
executeAsRoot("echo $value > $file")
}

private fun addOdinToolsToWhitelist() {
val currentWhitelist = getStringSystemSetting("app_whiteList", "")
if (currentWhitelist.contains(PACKAGE)) return
fun getIntValue(file: String, defaultValue: Int): Int {
return getValue(file)
.mapCatching { it?.toInt() ?: defaultValue }
.getOrDefault(defaultValue)
}

val newWhitelist = "$PACKAGE,$currentWhitelist".trimEnd(',')
setStringSystemSetting("app_whiteList", newWhitelist)
fun setIntValue(file: String, value: Int) {
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)
}
}