mirror of
https://github.com/PenumbraOS/sdk.git
synced 2026-02-03 17:26:48 -06:00
Working setting storage
This commit is contained in:
parent
7af56c4f5d
commit
52722f5b2c
@ -1,7 +1,7 @@
|
||||
package com.penumbraos.bridge_settings
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.media.AudioManager
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
@ -16,17 +16,12 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.booleanOrNull
|
||||
import kotlinx.serialization.json.doubleOrNull
|
||||
import kotlinx.serialization.json.intOrNull
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import java.io.File
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
private const val TAG = "SettingsRegistry"
|
||||
|
||||
private const val SYSTEM_SETTINGS_APP_ID = "penumbra_system"
|
||||
|
||||
data class ActionResult(
|
||||
val success: Boolean,
|
||||
val message: String? = null,
|
||||
@ -49,9 +44,13 @@ interface SettingsActionProvider {
|
||||
fun getActionDefinitions(): Map<String, LocalActionDefinition>
|
||||
}
|
||||
|
||||
class SettingsRegistry(private val context: Context, val shellClient: ShellClient) {
|
||||
class SettingsRegistry(
|
||||
private val context: Context,
|
||||
private val sharedPreferences: SharedPreferences,
|
||||
shellClient: ShellClient
|
||||
) {
|
||||
private val appSettings = ConcurrentHashMap<String, MutableMap<String, AppSettingsCategory>>()
|
||||
private val systemSettings = ConcurrentHashMap<String, Any>()
|
||||
private val systemSettings = ConcurrentHashMap<String, Any?>()
|
||||
private val actionProviders = ConcurrentHashMap<String, SettingsActionProvider>()
|
||||
|
||||
// Execution state tracking
|
||||
@ -102,9 +101,6 @@ class SettingsRegistry(private val context: Context, val shellClient: ShellClien
|
||||
}
|
||||
}
|
||||
|
||||
// Store saved app settings values until apps register their schemas
|
||||
private val savedAppSettingsValues =
|
||||
ConcurrentHashMap<String, ConcurrentHashMap<String, Map<String, JsonElement>>>()
|
||||
|
||||
private val humaneDisplayController = HumaneDisplayController(shellClient)
|
||||
private val temperatureController = TemperatureController(shellClient)
|
||||
@ -113,11 +109,9 @@ class SettingsRegistry(private val context: Context, val shellClient: ShellClien
|
||||
// Reference to web server for broadcasting (set by SettingsService)
|
||||
private var webServer: SettingsWebServer? = null
|
||||
|
||||
private val _settingsFlow = MutableStateFlow<Map<String, Map<String, Any>>>(emptyMap())
|
||||
val settingsFlow: StateFlow<Map<String, Map<String, Any>>> = _settingsFlow.asStateFlow()
|
||||
private val _settingsFlow = MutableStateFlow<Map<String, Map<String, Any?>>>(emptyMap())
|
||||
val settingsFlow: StateFlow<Map<String, Map<String, Any?>>> = _settingsFlow.asStateFlow()
|
||||
|
||||
@SuppressLint("SdCardPath")
|
||||
private val settingsFile = File("/sdcard/penumbra/etc/settings.json")
|
||||
private val json = Json {
|
||||
prettyPrint = true
|
||||
ignoreUnknownKeys = true
|
||||
@ -142,30 +136,16 @@ class SettingsRegistry(private val context: Context, val shellClient: ShellClien
|
||||
|
||||
private fun loadSavedSettings() {
|
||||
try {
|
||||
if (settingsFile.exists()) {
|
||||
val persistedData =
|
||||
json.decodeFromString<PersistedSettings>(settingsFile.readText())
|
||||
|
||||
// Load non-Android system settings
|
||||
persistedData.systemSettings.forEach { (key, value) ->
|
||||
sharedPreferences.all.filter { it.key.startsWith(SYSTEM_SETTINGS_APP_ID) }
|
||||
.forEach { (key, value) ->
|
||||
if (!isAndroidSystemSetting(key)) {
|
||||
systemSettings[key] = value // Keep as string
|
||||
systemSettings[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
// Load app settings values (without schemas - will be merged when apps register)
|
||||
persistedData.appSettings.forEach { (appId, categories) ->
|
||||
val appSavedValues =
|
||||
savedAppSettingsValues.getOrPut(appId) { ConcurrentHashMap() }
|
||||
categories.forEach { (category, settingValues) ->
|
||||
appSavedValues[category] = settingValues
|
||||
}
|
||||
}
|
||||
|
||||
Log.i(TAG, "Loaded settings from ${settingsFile.absolutePath}")
|
||||
}
|
||||
Log.i(TAG, "Loaded settings from SharedPreferences")
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to load settings from file", e)
|
||||
Log.w(TAG, "Failed to load settings from SharedPreferences", e)
|
||||
}
|
||||
}
|
||||
|
||||
@ -288,26 +268,22 @@ class SettingsRegistry(private val context: Context, val shellClient: ShellClien
|
||||
category: String,
|
||||
definitions: Map<String, SettingDefinition>
|
||||
) {
|
||||
if (appId.startsWith(SYSTEM_SETTINGS_APP_ID)) {
|
||||
throw IllegalArgumentException("Cannot register system settings")
|
||||
}
|
||||
|
||||
Log.i(TAG, "Registering settings for app: $appId, category: $category")
|
||||
|
||||
val appCategories = appSettings.getOrPut(appId) { mutableMapOf() }
|
||||
val settingsCategory = AppSettingsCategory(appId, category, definitions)
|
||||
|
||||
// Initialize with default values
|
||||
definitions.forEach { (key, definition) ->
|
||||
settingsCategory.values[key] = definition.defaultValue
|
||||
}
|
||||
|
||||
// Merge in any previously saved values for this app/category
|
||||
savedAppSettingsValues[appId]?.get(category)?.forEach { (key, jsonValue) ->
|
||||
if (definitions.containsKey(key)) {
|
||||
// Convert JsonElement back to proper type
|
||||
val convertedValue = jsonValue.jsonPrimitive.let { primitive ->
|
||||
primitive.booleanOrNull ?: primitive.intOrNull ?: primitive.doubleOrNull
|
||||
?: primitive.content
|
||||
}
|
||||
settingsCategory.values[key] = convertedValue
|
||||
Log.d(TAG, "Restored saved value for $appId.$category.$key = $convertedValue")
|
||||
val fullKey = "$appId.$category.$key"
|
||||
val savedValue = sharedPreferences.all[fullKey]
|
||||
if (savedValue != null) {
|
||||
settingsCategory.values[key] = savedValue
|
||||
} else {
|
||||
settingsCategory.values[key] = definition.defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
@ -336,8 +312,8 @@ class SettingsRegistry(private val context: Context, val shellClient: ShellClien
|
||||
|
||||
settingsCategory.values[key] = value
|
||||
|
||||
// Save app settings to file
|
||||
saveSettings()
|
||||
// Save this specific app setting immediately
|
||||
saveAppSetting(appId, category, key, value)
|
||||
|
||||
updateSettingsFlow()
|
||||
Log.i(TAG, "Updated app setting: $appId.$category.$key = $value")
|
||||
@ -366,7 +342,7 @@ class SettingsRegistry(private val context: Context, val shellClient: ShellClien
|
||||
if (success) {
|
||||
if (!isAndroidSystemSetting(key)) {
|
||||
systemSettings[key] = value
|
||||
saveSettings()
|
||||
saveSystemSetting(key, value)
|
||||
}
|
||||
|
||||
updateSettingsFlow()
|
||||
@ -381,12 +357,12 @@ class SettingsRegistry(private val context: Context, val shellClient: ShellClien
|
||||
return systemSettings[key]
|
||||
}
|
||||
|
||||
fun getAllSystemSettings(): Map<String, Any> {
|
||||
fun getAllSystemSettings(): Map<String, Any?> {
|
||||
return systemSettings.toMap()
|
||||
}
|
||||
|
||||
fun getAllSettings(): Map<String, Map<String, Any>> {
|
||||
val result = mutableMapOf<String, Map<String, Any>>()
|
||||
fun getAllSettings(): Map<String, Map<String, Any?>> {
|
||||
val result = mutableMapOf<String, Map<String, Any?>>()
|
||||
|
||||
// Add system settings
|
||||
result["system"] = systemSettings.toMap()
|
||||
@ -444,44 +420,40 @@ class SettingsRegistry(private val context: Context, val shellClient: ShellClien
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveSettings() {
|
||||
private fun SharedPreferences.Editor.putValue(key: String, value: Any) {
|
||||
when (value) {
|
||||
is Boolean -> putBoolean(key, value)
|
||||
is Int -> putInt(key, value)
|
||||
is Long -> putLong(key, value)
|
||||
is Float -> putFloat(key, value)
|
||||
is String -> putString(key, value)
|
||||
else -> putString(key, value.toString())
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveSystemSetting(key: String, value: Any) {
|
||||
val fullKey = "$SYSTEM_SETTINGS_APP_ID.$key"
|
||||
try {
|
||||
// Create directory if it doesn't exist
|
||||
settingsFile.parentFile?.mkdirs()
|
||||
|
||||
// Collect non-Android system settings for persistence
|
||||
val systemSettingsToSave = systemSettings
|
||||
.filterKeys { !isAndroidSystemSetting(it) }
|
||||
.mapValues { it.value.toString() }
|
||||
|
||||
// Serialize app settings
|
||||
val appSettingsToSave = mutableMapOf<String, Map<String, Map<String, JsonElement>>>()
|
||||
appSettings.forEach { (appId, categories) ->
|
||||
val categoriesMap = mutableMapOf<String, Map<String, JsonElement>>()
|
||||
categories.forEach { (category, categoryData) ->
|
||||
val valuesMap = mutableMapOf<String, JsonElement>()
|
||||
categoryData.values.forEach { (key, value) ->
|
||||
valuesMap[key] = when (value) {
|
||||
is Boolean -> JsonPrimitive(value)
|
||||
is Number -> JsonPrimitive(value)
|
||||
is String -> JsonPrimitive(value)
|
||||
else -> JsonPrimitive(value.toString())
|
||||
}
|
||||
}
|
||||
categoriesMap[category] = valuesMap
|
||||
}
|
||||
appSettingsToSave[appId] = categoriesMap
|
||||
sharedPreferences.edit().apply {
|
||||
putValue(fullKey, value)
|
||||
apply()
|
||||
}
|
||||
|
||||
val persistedData = PersistedSettings(
|
||||
systemSettings = systemSettingsToSave,
|
||||
appSettings = appSettingsToSave
|
||||
)
|
||||
|
||||
settingsFile.writeText(json.encodeToString(persistedData))
|
||||
Log.d(TAG, "Settings saved to ${settingsFile.absolutePath}")
|
||||
Log.d(TAG, "Saved system setting: $fullKey = $value")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to save settings to file", e)
|
||||
Log.e(TAG, "Failed to save system setting $fullKey", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveAppSetting(appId: String, category: String, key: String, value: Any) {
|
||||
val fullKey = "$appId.$category.$key"
|
||||
try {
|
||||
sharedPreferences.edit().apply {
|
||||
putValue(fullKey, value)
|
||||
apply()
|
||||
}
|
||||
Log.d(TAG, "Saved app setting: $fullKey = $value")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to save app setting $fullKey", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.penumbraos.bridge_settings
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import com.penumbraos.appprocessmocks.Common
|
||||
import com.penumbraos.appprocessmocks.MockContext
|
||||
@ -14,6 +15,7 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
|
||||
private const val TAG = "SettingsService"
|
||||
|
||||
@ -35,6 +37,22 @@ class SettingsService {
|
||||
val context =
|
||||
MockContext.createWithAppContext(classLoader, thread, "com.android.settings")
|
||||
|
||||
val settingsFile = File("/data/misc/user/0/penumbra/settings.xml")
|
||||
settingsFile.mkdirs()
|
||||
|
||||
val contextImplClass = classLoader.loadClass("android.app.ContextImpl")
|
||||
val getSharedPreferencesByFileMethod = contextImplClass.getDeclaredMethod(
|
||||
"getSharedPreferences",
|
||||
File::class.java, Int::class.java
|
||||
)
|
||||
getSharedPreferencesByFileMethod.isAccessible = true
|
||||
|
||||
val sharedPreferences =
|
||||
getSharedPreferencesByFileMethod.invoke(
|
||||
context.baseContext,
|
||||
settingsFile, 2
|
||||
) as SharedPreferences
|
||||
|
||||
// Connect to bridge and get ShellClient
|
||||
val bridge = connectToBridge(TAG, context)
|
||||
Log.i(TAG, "Connected to bridge-core")
|
||||
@ -46,7 +64,7 @@ class SettingsService {
|
||||
Log.i(TAG, "Created ShellClient")
|
||||
|
||||
// Initialize components with context and shell client
|
||||
settingsRegistry = SettingsRegistry(context, shellClient)
|
||||
settingsRegistry = SettingsRegistry(context, sharedPreferences, shellClient)
|
||||
settingsRegistry.initialize()
|
||||
settingsProvider = SettingsProvider(settingsRegistry)
|
||||
|
||||
|
||||
@ -424,7 +424,7 @@ class SettingsWebServer(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun broadcastSettingsUpdate(allSettings: Map<String, Map<String, Any>>) {
|
||||
private suspend fun broadcastSettingsUpdate(allSettings: Map<String, Map<String, Any?>>) {
|
||||
val message = StatusMessage.AllSettings(allSettings.toJsonElement())
|
||||
broadcast(message)
|
||||
}
|
||||
@ -443,7 +443,7 @@ class SettingsWebServer(
|
||||
Log.d(TAG, "Broadcasted app status update: $appId.$component")
|
||||
}
|
||||
|
||||
suspend fun broadcastAppEvent(appId: String, eventType: String, payload: Map<String, Any>) {
|
||||
suspend fun broadcastAppEvent(appId: String, eventType: String, payload: Map<String, Any?>) {
|
||||
val message = StatusMessage.AppEvent(
|
||||
appId = appId,
|
||||
eventType = eventType,
|
||||
|
||||
@ -16,7 +16,7 @@ robolectric = "4.14"
|
||||
okhttp = "4.12.0"
|
||||
ktor = "3.0.0"
|
||||
systemJars = "70382bc"
|
||||
appprocessmocks = "2af582c"
|
||||
appprocessmocks = "9f2a20a"
|
||||
dnsjava = "3.6.3"
|
||||
|
||||
[libraries]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user