Skip to content

Commit 52722f5

Browse files
committed
Working setting storage
1 parent 7af56c4 commit 52722f5

File tree

4 files changed

+84
-94
lines changed

4 files changed

+84
-94
lines changed

bridge-settings/src/main/java/com/penumbraos/bridge_settings/SettingsRegistry.kt

Lines changed: 62 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.penumbraos.bridge_settings
22

3-
import android.annotation.SuppressLint
43
import android.content.Context
4+
import android.content.SharedPreferences
55
import android.media.AudioManager
66
import android.os.Handler
77
import android.os.Looper
@@ -16,17 +16,12 @@ import kotlinx.coroutines.flow.StateFlow
1616
import kotlinx.coroutines.flow.asStateFlow
1717
import kotlinx.coroutines.launch
1818
import kotlinx.serialization.json.Json
19-
import kotlinx.serialization.json.JsonElement
20-
import kotlinx.serialization.json.JsonPrimitive
21-
import kotlinx.serialization.json.booleanOrNull
22-
import kotlinx.serialization.json.doubleOrNull
23-
import kotlinx.serialization.json.intOrNull
24-
import kotlinx.serialization.json.jsonPrimitive
25-
import java.io.File
2619
import java.util.concurrent.ConcurrentHashMap
2720

2821
private const val TAG = "SettingsRegistry"
2922

23+
private const val SYSTEM_SETTINGS_APP_ID = "penumbra_system"
24+
3025
data class ActionResult(
3126
val success: Boolean,
3227
val message: String? = null,
@@ -49,9 +44,13 @@ interface SettingsActionProvider {
4944
fun getActionDefinitions(): Map<String, LocalActionDefinition>
5045
}
5146

52-
class SettingsRegistry(private val context: Context, val shellClient: ShellClient) {
47+
class SettingsRegistry(
48+
private val context: Context,
49+
private val sharedPreferences: SharedPreferences,
50+
shellClient: ShellClient
51+
) {
5352
private val appSettings = ConcurrentHashMap<String, MutableMap<String, AppSettingsCategory>>()
54-
private val systemSettings = ConcurrentHashMap<String, Any>()
53+
private val systemSettings = ConcurrentHashMap<String, Any?>()
5554
private val actionProviders = ConcurrentHashMap<String, SettingsActionProvider>()
5655

5756
// Execution state tracking
@@ -102,9 +101,6 @@ class SettingsRegistry(private val context: Context, val shellClient: ShellClien
102101
}
103102
}
104103

105-
// Store saved app settings values until apps register their schemas
106-
private val savedAppSettingsValues =
107-
ConcurrentHashMap<String, ConcurrentHashMap<String, Map<String, JsonElement>>>()
108104

109105
private val humaneDisplayController = HumaneDisplayController(shellClient)
110106
private val temperatureController = TemperatureController(shellClient)
@@ -113,11 +109,9 @@ class SettingsRegistry(private val context: Context, val shellClient: ShellClien
113109
// Reference to web server for broadcasting (set by SettingsService)
114110
private var webServer: SettingsWebServer? = null
115111

116-
private val _settingsFlow = MutableStateFlow<Map<String, Map<String, Any>>>(emptyMap())
117-
val settingsFlow: StateFlow<Map<String, Map<String, Any>>> = _settingsFlow.asStateFlow()
112+
private val _settingsFlow = MutableStateFlow<Map<String, Map<String, Any?>>>(emptyMap())
113+
val settingsFlow: StateFlow<Map<String, Map<String, Any?>>> = _settingsFlow.asStateFlow()
118114

119-
@SuppressLint("SdCardPath")
120-
private val settingsFile = File("/sdcard/penumbra/etc/settings.json")
121115
private val json = Json {
122116
prettyPrint = true
123117
ignoreUnknownKeys = true
@@ -142,30 +136,16 @@ class SettingsRegistry(private val context: Context, val shellClient: ShellClien
142136

143137
private fun loadSavedSettings() {
144138
try {
145-
if (settingsFile.exists()) {
146-
val persistedData =
147-
json.decodeFromString<PersistedSettings>(settingsFile.readText())
148-
149-
// Load non-Android system settings
150-
persistedData.systemSettings.forEach { (key, value) ->
139+
sharedPreferences.all.filter { it.key.startsWith(SYSTEM_SETTINGS_APP_ID) }
140+
.forEach { (key, value) ->
151141
if (!isAndroidSystemSetting(key)) {
152-
systemSettings[key] = value // Keep as string
153-
}
154-
}
155-
156-
// Load app settings values (without schemas - will be merged when apps register)
157-
persistedData.appSettings.forEach { (appId, categories) ->
158-
val appSavedValues =
159-
savedAppSettingsValues.getOrPut(appId) { ConcurrentHashMap() }
160-
categories.forEach { (category, settingValues) ->
161-
appSavedValues[category] = settingValues
142+
systemSettings[key] = value
162143
}
163144
}
164145

165-
Log.i(TAG, "Loaded settings from ${settingsFile.absolutePath}")
166-
}
146+
Log.i(TAG, "Loaded settings from SharedPreferences")
167147
} catch (e: Exception) {
168-
Log.w(TAG, "Failed to load settings from file", e)
148+
Log.w(TAG, "Failed to load settings from SharedPreferences", e)
169149
}
170150
}
171151

@@ -288,26 +268,22 @@ class SettingsRegistry(private val context: Context, val shellClient: ShellClien
288268
category: String,
289269
definitions: Map<String, SettingDefinition>
290270
) {
271+
if (appId.startsWith(SYSTEM_SETTINGS_APP_ID)) {
272+
throw IllegalArgumentException("Cannot register system settings")
273+
}
274+
291275
Log.i(TAG, "Registering settings for app: $appId, category: $category")
292276

293277
val appCategories = appSettings.getOrPut(appId) { mutableMapOf() }
294278
val settingsCategory = AppSettingsCategory(appId, category, definitions)
295279

296-
// Initialize with default values
297280
definitions.forEach { (key, definition) ->
298-
settingsCategory.values[key] = definition.defaultValue
299-
}
300-
301-
// Merge in any previously saved values for this app/category
302-
savedAppSettingsValues[appId]?.get(category)?.forEach { (key, jsonValue) ->
303-
if (definitions.containsKey(key)) {
304-
// Convert JsonElement back to proper type
305-
val convertedValue = jsonValue.jsonPrimitive.let { primitive ->
306-
primitive.booleanOrNull ?: primitive.intOrNull ?: primitive.doubleOrNull
307-
?: primitive.content
308-
}
309-
settingsCategory.values[key] = convertedValue
310-
Log.d(TAG, "Restored saved value for $appId.$category.$key = $convertedValue")
281+
val fullKey = "$appId.$category.$key"
282+
val savedValue = sharedPreferences.all[fullKey]
283+
if (savedValue != null) {
284+
settingsCategory.values[key] = savedValue
285+
} else {
286+
settingsCategory.values[key] = definition.defaultValue
311287
}
312288
}
313289

@@ -336,8 +312,8 @@ class SettingsRegistry(private val context: Context, val shellClient: ShellClien
336312

337313
settingsCategory.values[key] = value
338314

339-
// Save app settings to file
340-
saveSettings()
315+
// Save this specific app setting immediately
316+
saveAppSetting(appId, category, key, value)
341317

342318
updateSettingsFlow()
343319
Log.i(TAG, "Updated app setting: $appId.$category.$key = $value")
@@ -366,7 +342,7 @@ class SettingsRegistry(private val context: Context, val shellClient: ShellClien
366342
if (success) {
367343
if (!isAndroidSystemSetting(key)) {
368344
systemSettings[key] = value
369-
saveSettings()
345+
saveSystemSetting(key, value)
370346
}
371347

372348
updateSettingsFlow()
@@ -381,12 +357,12 @@ class SettingsRegistry(private val context: Context, val shellClient: ShellClien
381357
return systemSettings[key]
382358
}
383359

384-
fun getAllSystemSettings(): Map<String, Any> {
360+
fun getAllSystemSettings(): Map<String, Any?> {
385361
return systemSettings.toMap()
386362
}
387363

388-
fun getAllSettings(): Map<String, Map<String, Any>> {
389-
val result = mutableMapOf<String, Map<String, Any>>()
364+
fun getAllSettings(): Map<String, Map<String, Any?>> {
365+
val result = mutableMapOf<String, Map<String, Any?>>()
390366

391367
// Add system settings
392368
result["system"] = systemSettings.toMap()
@@ -444,44 +420,40 @@ class SettingsRegistry(private val context: Context, val shellClient: ShellClien
444420
}
445421
}
446422

447-
private fun saveSettings() {
423+
private fun SharedPreferences.Editor.putValue(key: String, value: Any) {
424+
when (value) {
425+
is Boolean -> putBoolean(key, value)
426+
is Int -> putInt(key, value)
427+
is Long -> putLong(key, value)
428+
is Float -> putFloat(key, value)
429+
is String -> putString(key, value)
430+
else -> putString(key, value.toString())
431+
}
432+
}
433+
434+
private fun saveSystemSetting(key: String, value: Any) {
435+
val fullKey = "$SYSTEM_SETTINGS_APP_ID.$key"
448436
try {
449-
// Create directory if it doesn't exist
450-
settingsFile.parentFile?.mkdirs()
451-
452-
// Collect non-Android system settings for persistence
453-
val systemSettingsToSave = systemSettings
454-
.filterKeys { !isAndroidSystemSetting(it) }
455-
.mapValues { it.value.toString() }
456-
457-
// Serialize app settings
458-
val appSettingsToSave = mutableMapOf<String, Map<String, Map<String, JsonElement>>>()
459-
appSettings.forEach { (appId, categories) ->
460-
val categoriesMap = mutableMapOf<String, Map<String, JsonElement>>()
461-
categories.forEach { (category, categoryData) ->
462-
val valuesMap = mutableMapOf<String, JsonElement>()
463-
categoryData.values.forEach { (key, value) ->
464-
valuesMap[key] = when (value) {
465-
is Boolean -> JsonPrimitive(value)
466-
is Number -> JsonPrimitive(value)
467-
is String -> JsonPrimitive(value)
468-
else -> JsonPrimitive(value.toString())
469-
}
470-
}
471-
categoriesMap[category] = valuesMap
472-
}
473-
appSettingsToSave[appId] = categoriesMap
437+
sharedPreferences.edit().apply {
438+
putValue(fullKey, value)
439+
apply()
474440
}
441+
Log.d(TAG, "Saved system setting: $fullKey = $value")
442+
} catch (e: Exception) {
443+
Log.e(TAG, "Failed to save system setting $fullKey", e)
444+
}
445+
}
475446

476-
val persistedData = PersistedSettings(
477-
systemSettings = systemSettingsToSave,
478-
appSettings = appSettingsToSave
479-
)
480-
481-
settingsFile.writeText(json.encodeToString(persistedData))
482-
Log.d(TAG, "Settings saved to ${settingsFile.absolutePath}")
447+
private fun saveAppSetting(appId: String, category: String, key: String, value: Any) {
448+
val fullKey = "$appId.$category.$key"
449+
try {
450+
sharedPreferences.edit().apply {
451+
putValue(fullKey, value)
452+
apply()
453+
}
454+
Log.d(TAG, "Saved app setting: $fullKey = $value")
483455
} catch (e: Exception) {
484-
Log.e(TAG, "Failed to save settings to file", e)
456+
Log.e(TAG, "Failed to save app setting $fullKey", e)
485457
}
486458
}
487459

bridge-settings/src/main/java/com/penumbraos/bridge_settings/SettingsService.kt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.penumbraos.bridge_settings
22

3+
import android.content.SharedPreferences
34
import android.util.Log
45
import com.penumbraos.appprocessmocks.Common
56
import com.penumbraos.appprocessmocks.MockContext
@@ -14,6 +15,7 @@ import kotlinx.coroutines.CoroutineScope
1415
import kotlinx.coroutines.Dispatchers
1516
import kotlinx.coroutines.SupervisorJob
1617
import kotlinx.coroutines.launch
18+
import java.io.File
1719

1820
private const val TAG = "SettingsService"
1921

@@ -35,6 +37,22 @@ class SettingsService {
3537
val context =
3638
MockContext.createWithAppContext(classLoader, thread, "com.android.settings")
3739

40+
val settingsFile = File("/data/misc/user/0/penumbra/settings.xml")
41+
settingsFile.mkdirs()
42+
43+
val contextImplClass = classLoader.loadClass("android.app.ContextImpl")
44+
val getSharedPreferencesByFileMethod = contextImplClass.getDeclaredMethod(
45+
"getSharedPreferences",
46+
File::class.java, Int::class.java
47+
)
48+
getSharedPreferencesByFileMethod.isAccessible = true
49+
50+
val sharedPreferences =
51+
getSharedPreferencesByFileMethod.invoke(
52+
context.baseContext,
53+
settingsFile, 2
54+
) as SharedPreferences
55+
3856
// Connect to bridge and get ShellClient
3957
val bridge = connectToBridge(TAG, context)
4058
Log.i(TAG, "Connected to bridge-core")
@@ -46,7 +64,7 @@ class SettingsService {
4664
Log.i(TAG, "Created ShellClient")
4765

4866
// Initialize components with context and shell client
49-
settingsRegistry = SettingsRegistry(context, shellClient)
67+
settingsRegistry = SettingsRegistry(context, sharedPreferences, shellClient)
5068
settingsRegistry.initialize()
5169
settingsProvider = SettingsProvider(settingsRegistry)
5270

bridge-settings/src/main/java/com/penumbraos/bridge_settings/SettingsWebServer.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ class SettingsWebServer(
424424
}
425425
}
426426

427-
private suspend fun broadcastSettingsUpdate(allSettings: Map<String, Map<String, Any>>) {
427+
private suspend fun broadcastSettingsUpdate(allSettings: Map<String, Map<String, Any?>>) {
428428
val message = StatusMessage.AllSettings(allSettings.toJsonElement())
429429
broadcast(message)
430430
}
@@ -443,7 +443,7 @@ class SettingsWebServer(
443443
Log.d(TAG, "Broadcasted app status update: $appId.$component")
444444
}
445445

446-
suspend fun broadcastAppEvent(appId: String, eventType: String, payload: Map<String, Any>) {
446+
suspend fun broadcastAppEvent(appId: String, eventType: String, payload: Map<String, Any?>) {
447447
val message = StatusMessage.AppEvent(
448448
appId = appId,
449449
eventType = eventType,

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ robolectric = "4.14"
1616
okhttp = "4.12.0"
1717
ktor = "3.0.0"
1818
systemJars = "70382bc"
19-
appprocessmocks = "2af582c"
19+
appprocessmocks = "9f2a20a"
2020
dnsjava = "3.6.3"
2121

2222
[libraries]

0 commit comments

Comments
 (0)