refactor: file handle manager

This commit is contained in:
rhunk 2024-05-28 23:41:52 +02:00
parent 84fc7c6ee3
commit c31e6fd0c2
25 changed files with 329 additions and 303 deletions

View File

@ -0,0 +1,87 @@
package me.rhunk.snapenhance
import android.os.ParcelFileDescriptor
import me.rhunk.snapenhance.bridge.storage.FileHandle
import me.rhunk.snapenhance.bridge.storage.FileHandleManager
import me.rhunk.snapenhance.common.bridge.FileHandleScope
import me.rhunk.snapenhance.common.bridge.InternalFileHandleType
import me.rhunk.snapenhance.common.bridge.wrapper.LocaleWrapper
import me.rhunk.snapenhance.common.logger.AbstractLogger
import me.rhunk.snapenhance.common.util.ktx.toParcelFileDescriptor
import java.io.File
class LocalFileHandle(
private val file: File
): FileHandle.Stub() {
override fun exists() = file.exists()
override fun create() = file.createNewFile()
override fun delete() = file.delete()
override fun open(mode: Int): ParcelFileDescriptor? {
return runCatching {
ParcelFileDescriptor.open(file, mode)
}.onFailure {
AbstractLogger.directError("Failed to open file handle: ${it.message}", it)
}.getOrNull()
}
}
class AssetFileHandle(
private val context: RemoteSideContext,
private val assetPath: String
): FileHandle.Stub() {
override fun exists() = true
override fun create() = false
override fun delete() = false
override fun open(mode: Int): ParcelFileDescriptor? {
return runCatching {
context.androidContext.assets.open(assetPath).toParcelFileDescriptor(context.coroutineScope)
}.onFailure {
AbstractLogger.directError("Failed to open asset handle: ${it.message}", it)
}.getOrNull()
}
}
class RemoteFileHandleManager(
private val context: RemoteSideContext
): FileHandleManager.Stub() {
override fun getFileHandle(scope: String, name: String): FileHandle? {
val fileHandleScope = FileHandleScope.fromValue(scope) ?: run {
context.log.error("invalid file handle scope: $scope", "FileHandleManager")
return null
}
when (fileHandleScope) {
FileHandleScope.INTERNAL -> {
val fileHandleType = InternalFileHandleType.fromValue(name) ?: run {
context.log.error("invalid file handle name: $name", "FileHandleManager")
return null
}
return LocalFileHandle(
fileHandleType.resolve(context.androidContext)
)
}
FileHandleScope.LOCALE -> {
val foundLocale = context.androidContext.resources.assets.list("lang")?.firstOrNull {
it.startsWith(name)
}?.substringBefore(".") ?: return null
if (name == LocaleWrapper.DEFAULT_LOCALE) {
return AssetFileHandle(
context,
"lang/${LocaleWrapper.DEFAULT_LOCALE}.json"
)
}
return AssetFileHandle(
context,
"lang/$foundLocale.json"
)
}
else -> return null
}
}
}

View File

@ -23,7 +23,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import me.rhunk.snapenhance.bridge.BridgeService import me.rhunk.snapenhance.bridge.BridgeService
import me.rhunk.snapenhance.common.BuildConfig import me.rhunk.snapenhance.common.BuildConfig
import me.rhunk.snapenhance.common.bridge.types.BridgeFileType
import me.rhunk.snapenhance.common.bridge.wrapper.LocaleWrapper import me.rhunk.snapenhance.common.bridge.wrapper.LocaleWrapper
import me.rhunk.snapenhance.common.bridge.wrapper.LoggerWrapper import me.rhunk.snapenhance.common.bridge.wrapper.LoggerWrapper
import me.rhunk.snapenhance.common.bridge.wrapper.MappingsWrapper import me.rhunk.snapenhance.common.bridge.wrapper.MappingsWrapper
@ -60,9 +59,10 @@ class RemoteSideContext(
set(value) { _activity?.clear(); _activity = WeakReference(value) } set(value) { _activity?.clear(); _activity = WeakReference(value) }
val sharedPreferences: SharedPreferences get() = androidContext.getSharedPreferences("prefs", 0) val sharedPreferences: SharedPreferences get() = androidContext.getSharedPreferences("prefs", 0)
val config = ModConfig(androidContext) val fileHandleManager = RemoteFileHandleManager(this)
val translation = LocaleWrapper() val config = ModConfig(androidContext, fileHandleManager)
val mappings = MappingsWrapper() val translation = LocaleWrapper(fileHandleManager)
val mappings = MappingsWrapper(fileHandleManager)
val taskManager = TaskManager(this) val taskManager = TaskManager(this)
val database = AppDatabase(this) val database = AppDatabase(this)
val streaksReminder = StreaksReminder(this) val streaksReminder = StreaksReminder(this)
@ -70,7 +70,7 @@ class RemoteSideContext(
val scriptManager = RemoteScriptManager(this) val scriptManager = RemoteScriptManager(this)
val settingsOverlay = SettingsOverlay(this) val settingsOverlay = SettingsOverlay(this)
val e2eeImplementation = E2EEImplementation(this) val e2eeImplementation = E2EEImplementation(this)
val messageLogger by lazy { LoggerWrapper(androidContext.getDatabasePath(BridgeFileType.MESSAGE_LOGGER_DATABASE.fileName)) } val messageLogger by lazy { LoggerWrapper(androidContext) }
val tracker = RemoteTracker(this) val tracker = RemoteTracker(this)
val accountStorage = RemoteAccountStorage(this) val accountStorage = RemoteAccountStorage(this)
@ -99,16 +99,15 @@ class RemoteSideContext(
runBlocking(Dispatchers.IO) { runBlocking(Dispatchers.IO) {
log.init() log.init()
log.verbose("Loading RemoteSideContext") log.verbose("Loading RemoteSideContext")
config.loadFromContext(androidContext) config.load()
launch { launch {
mappings.apply { mappings.apply {
loadFromContext(androidContext)
init(androidContext) init(androidContext)
} }
} }
translation.apply { translation.apply {
userLocale = config.locale userLocale = config.locale
loadFromContext(androidContext) load()
} }
database.init() database.init()
streaksReminder.init() streaksReminder.init()

View File

@ -8,9 +8,6 @@ import kotlinx.coroutines.runBlocking
import me.rhunk.snapenhance.RemoteSideContext import me.rhunk.snapenhance.RemoteSideContext
import me.rhunk.snapenhance.SharedContextHolder import me.rhunk.snapenhance.SharedContextHolder
import me.rhunk.snapenhance.bridge.snapclient.MessagingBridge import me.rhunk.snapenhance.bridge.snapclient.MessagingBridge
import me.rhunk.snapenhance.common.bridge.types.BridgeFileType
import me.rhunk.snapenhance.common.bridge.types.FileActionType
import me.rhunk.snapenhance.common.bridge.wrapper.LocaleWrapper
import me.rhunk.snapenhance.common.data.MessagingFriendInfo import me.rhunk.snapenhance.common.data.MessagingFriendInfo
import me.rhunk.snapenhance.common.data.MessagingGroupInfo import me.rhunk.snapenhance.common.data.MessagingGroupInfo
import me.rhunk.snapenhance.common.data.SocialScope import me.rhunk.snapenhance.common.data.SocialScope
@ -84,54 +81,11 @@ class BridgeService : Service() {
} }
inner class BridgeBinder : BridgeInterface.Stub() { inner class BridgeBinder : BridgeInterface.Stub() {
override fun getApplicationApkPath(): String = applicationInfo.publicSourceDir
override fun broadcastLog(tag: String, level: String, message: String) { override fun broadcastLog(tag: String, level: String, message: String) {
remoteSideContext.log.internalLog(tag, LogLevel.fromShortName(level) ?: LogLevel.INFO, message) remoteSideContext.log.internalLog(tag, LogLevel.fromShortName(level) ?: LogLevel.INFO, message)
} }
override fun fileOperation(action: Int, fileType: Int, content: ByteArray?): ByteArray {
val resolvedFile = BridgeFileType.fromValue(fileType)?.resolve(this@BridgeService)
return when (FileActionType.entries[action]) {
FileActionType.CREATE_AND_READ -> {
resolvedFile?.let {
if (!it.exists()) {
return content?.also { content -> it.writeBytes(content) } ?: ByteArray(
0
)
}
it.readBytes()
} ?: ByteArray(0)
}
FileActionType.READ -> {
resolvedFile?.takeIf { it.exists() }?.readBytes() ?: ByteArray(0)
}
FileActionType.WRITE -> {
content?.also { resolvedFile?.writeBytes(content) } ?: ByteArray(0)
}
FileActionType.DELETE -> {
resolvedFile?.takeIf { it.exists() }?.delete()
ByteArray(0)
}
FileActionType.EXISTS -> {
if (resolvedFile?.exists() == true)
ByteArray(1)
else ByteArray(0)
}
}
}
override fun getApplicationApkPath(): String = applicationInfo.publicSourceDir
override fun fetchLocales(userLocale: String) =
LocaleWrapper.fetchLocales(context = this@BridgeService, userLocale).associate {
it.locale to it.content
}
override fun enqueueDownload(intent: Intent, callback: DownloadCallback) { override fun enqueueDownload(intent: Intent, callback: DownloadCallback) {
DownloadProcessor( DownloadProcessor(
remoteSideContext = remoteSideContext, remoteSideContext = remoteSideContext,
@ -242,6 +196,8 @@ class BridgeService : Service() {
override fun getLogger() = remoteSideContext.messageLogger override fun getLogger() = remoteSideContext.messageLogger
override fun getTracker() = remoteSideContext.tracker override fun getTracker() = remoteSideContext.tracker
override fun getAccountStorage() = remoteSideContext.accountStorage override fun getAccountStorage() = remoteSideContext.accountStorage
override fun getFileHandleManager() = remoteSideContext.fileHandleManager
override fun registerMessagingBridge(bridge: MessagingBridge) { override fun registerMessagingBridge(bridge: MessagingBridge) {
messagingBridge = bridge messagingBridge = bridge
} }

View File

@ -222,9 +222,7 @@ class LoggerHistoryRoot : Routes.Route() {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
override val content: @Composable (NavBackStackEntry) -> Unit = { override val content: @Composable (NavBackStackEntry) -> Unit = {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
loggerWrapper = LoggerWrapper( loggerWrapper = LoggerWrapper(context.androidContext)
context.androidContext.getDatabasePath("message_logger.db")
)
} }
Column { Column {

View File

@ -17,12 +17,10 @@ import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.navigation.NavBackStackEntry import androidx.navigation.NavBackStackEntry
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import me.rhunk.snapenhance.common.Constants import me.rhunk.snapenhance.common.Constants
import me.rhunk.snapenhance.common.action.EnumAction import me.rhunk.snapenhance.common.action.EnumAction
import me.rhunk.snapenhance.common.bridge.types.BridgeFileType import me.rhunk.snapenhance.common.bridge.InternalFileHandleType
import me.rhunk.snapenhance.common.ui.rememberAsyncMutableState import me.rhunk.snapenhance.common.ui.rememberAsyncMutableState
import me.rhunk.snapenhance.ui.manager.Routes import me.rhunk.snapenhance.ui.manager.Routes
import me.rhunk.snapenhance.ui.setup.Requirements import me.rhunk.snapenhance.ui.setup.Requirements
@ -239,7 +237,7 @@ class HomeSettings : Routes.Route() {
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
var selectedFileType by remember { mutableStateOf(BridgeFileType.entries.first()) } var selectedFileType by remember { mutableStateOf(InternalFileHandleType.entries.first()) }
Box( Box(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
@ -253,19 +251,19 @@ class HomeSettings : Routes.Route() {
modifier = Modifier.fillMaxWidth(0.7f) modifier = Modifier.fillMaxWidth(0.7f)
) { ) {
TextField( TextField(
value = selectedFileType.displayName, value = selectedFileType.fileName,
onValueChange = {}, onValueChange = {},
readOnly = true, readOnly = true,
modifier = Modifier.menuAnchor() modifier = Modifier.menuAnchor()
) )
ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
BridgeFileType.entries.forEach { fileType -> InternalFileHandleType.entries.forEach { fileType ->
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
expanded = false expanded = false
selectedFileType = fileType selectedFileType = fileType
}, text = { }, text = {
Text(text = fileType.displayName) Text(text = fileType.fileName)
}) })
} }
} }

View File

@ -46,14 +46,13 @@ class PickLanguageScreen : SetupScreen(){
} }
private fun reloadTranslation(selectedLocale: String) { private fun reloadTranslation(selectedLocale: String) {
context.translation.reloadFromContext(context.androidContext, selectedLocale) context.translation.reload(selectedLocale)
} }
private fun setLocale(locale: String) { private fun setLocale(locale: String) {
with(context) { with(context) {
config.locale = locale config.locale = locale
config.writeConfig() config.writeConfig()
translation.reloadFromContext(androidContext, locale)
reloadTranslation(locale) reloadTranslation(locale)
} }
} }

View File

@ -10,30 +10,18 @@ import me.rhunk.snapenhance.bridge.logger.TrackerInterface;
import me.rhunk.snapenhance.bridge.ConfigStateListener; import me.rhunk.snapenhance.bridge.ConfigStateListener;
import me.rhunk.snapenhance.bridge.snapclient.MessagingBridge; import me.rhunk.snapenhance.bridge.snapclient.MessagingBridge;
import me.rhunk.snapenhance.bridge.AccountStorage; import me.rhunk.snapenhance.bridge.AccountStorage;
import me.rhunk.snapenhance.bridge.storage.FileHandleManager;
interface BridgeInterface { interface BridgeInterface {
/**
* broadcast a log message
*/
oneway void broadcastLog(String tag, String level, String message);
/**
* Execute a file operation
* @param fileType the corresponding file type (see BridgeFileType)
*/
byte[] fileOperation(int action, int fileType, in @nullable byte[] content);
/** /**
* Get the application APK path (assets for the conversation exporter) * Get the application APK path (assets for the conversation exporter)
*/ */
String getApplicationApkPath(); String getApplicationApkPath();
/** /**
* Fetch the locales * broadcast a log message
* */
* @return the map of locales (key: locale short name, value: locale data as json) oneway void broadcastLog(String tag, String level, String message);
*/
Map<String, ParcelFileDescriptor> fetchLocales(String userLocale);
/** /**
* Enqueue a download * Enqueue a download
@ -92,6 +80,8 @@ interface BridgeInterface {
AccountStorage getAccountStorage(); AccountStorage getAccountStorage();
FileHandleManager getFileHandleManager();
oneway void registerMessagingBridge(MessagingBridge bridge); oneway void registerMessagingBridge(MessagingBridge bridge);
oneway void openSettingsOverlay(); oneway void openSettingsOverlay();

View File

@ -0,0 +1,9 @@
package me.rhunk.snapenhance.bridge.storage;
interface FileHandle {
boolean exists();
boolean create();
boolean delete();
@nullable ParcelFileDescriptor open(int mode);
}

View File

@ -0,0 +1,7 @@
package me.rhunk.snapenhance.bridge.storage;
import me.rhunk.snapenhance.bridge.storage.FileHandle;
interface FileHandleManager {
@nullable FileHandle getFileHandle(String scope, String name);
}

View File

@ -0,0 +1,94 @@
package me.rhunk.snapenhance.common.bridge
import android.content.Context
import android.os.ParcelFileDescriptor
import android.os.ParcelFileDescriptor.AutoCloseInputStream
import android.os.ParcelFileDescriptor.AutoCloseOutputStream
import me.rhunk.snapenhance.bridge.storage.FileHandle
import java.io.File
enum class FileHandleScope(
val key: String
) {
INTERNAL("internal"),
LOCALE("locale");
companion object {
fun fromValue(name: String): FileHandleScope? = entries.find { it.key == name }
}
}
enum class InternalFileHandleType(
val key: String,
val fileName: String,
val isDatabase: Boolean = false
) {
CONFIG("config", "config.json"),
MAPPINGS("mappings", "mappings.json"),
MESSAGE_LOGGER("message_logger", "message_logger.db", isDatabase = true),
SUSPEND_LOCATION_STATE("suspend_location_state", "suspend_location_state.txt"),
PINNED_BEST_FRIEND("pinned_best_friend", "pinned_best_friend.txt");
fun resolve(context: Context): File = if (isDatabase) {
context.getDatabasePath(fileName)
} else {
File(context.filesDir, fileName)
}
companion object {
fun fromValue(name: String): InternalFileHandleType? = entries.find { it.key == name }
}
}
fun FileHandle.toWrapper() = FileHandleWrapper(lazy { this })
open class FileHandleWrapper(
private val fileHandle: Lazy<FileHandle>
) {
fun exists() = fileHandle.value.exists()
fun create() = fileHandle.value.create()
fun delete() = fileHandle.value.delete()
fun writeBytes(data: ByteArray) = fileHandle.value.open(
ParcelFileDescriptor.MODE_WRITE_ONLY or
ParcelFileDescriptor.MODE_CREATE or
ParcelFileDescriptor.MODE_TRUNCATE
).use { pfd ->
AutoCloseOutputStream(pfd).use {
it.write(data)
}
}
open fun readBytes(): ByteArray = fileHandle.value.open(
ParcelFileDescriptor.MODE_READ_ONLY or
ParcelFileDescriptor.MODE_CREATE
).use { pfd ->
AutoCloseInputStream(pfd).use {
it.readBytes()
}
}
fun inputStream(block: (AutoCloseInputStream) -> Unit) = fileHandle.value.open(
ParcelFileDescriptor.MODE_READ_ONLY or
ParcelFileDescriptor.MODE_CREATE
).use { pfd ->
AutoCloseInputStream(pfd).use {
block(it)
}
}
fun outputStream(block: (AutoCloseOutputStream) -> Unit) = fileHandle.value.open(
ParcelFileDescriptor.MODE_WRITE_ONLY or
ParcelFileDescriptor.MODE_CREATE or
ParcelFileDescriptor.MODE_TRUNCATE
).use { pfd ->
AutoCloseOutputStream(pfd).use {
block(it)
}
}
}

View File

@ -1,29 +0,0 @@
package me.rhunk.snapenhance.common.bridge
import android.content.Context
import me.rhunk.snapenhance.common.bridge.types.BridgeFileType
open class FileLoaderWrapper(
val fileType: BridgeFileType,
val defaultContent: ByteArray
) {
lateinit var isFileExists: () -> Boolean
lateinit var write: (ByteArray) -> Unit
lateinit var read: () -> ByteArray
lateinit var delete: () -> Unit
fun loadFromContext(context: Context) {
val file = fileType.resolve(context)
isFileExists = { file.exists() }
read = {
if (!file.exists()) {
file.createNewFile()
file.writeBytes("{}".toByteArray(Charsets.UTF_8))
}
file.readBytes()
}
write = { file.writeBytes(it) }
delete = { file.delete() }
}
}

View File

@ -0,0 +1,17 @@
package me.rhunk.snapenhance.common.bridge
import me.rhunk.snapenhance.bridge.storage.FileHandleManager
open class InternalFileWrapper(
fileHandleManager: FileHandleManager,
private val fileType: InternalFileHandleType,
val defaultValue: String? = null
): FileHandleWrapper(lazy { fileHandleManager.getFileHandle(FileHandleScope.INTERNAL.key, fileType.key)!! }) {
override fun readBytes(): ByteArray {
val bytes = super.readBytes()
if (bytes.isEmpty()) {
defaultValue?.let { writeBytes(it.toByteArray()) }
}
return super.readBytes()
}
}

View File

@ -1,26 +0,0 @@
package me.rhunk.snapenhance.common.bridge.types
import android.content.Context
import java.io.File
enum class BridgeFileType(val value: Int, val fileName: String, val displayName: String, private val isDatabase: Boolean = false) {
CONFIG(0, "config.json", "Config"),
MAPPINGS(1, "mappings.json", "Mappings"),
MESSAGE_LOGGER_DATABASE(2, "message_logger.db", "Message Logger",true),
PINNED_CONVERSATIONS(3, "pinned_conversations.txt", "Pinned Conversations"),
SUSPEND_LOCATION_STATE(4, "suspend_location_state.txt", "Suspend Location State"),
PINNED_BEST_FRIEND(5, "pinned_best_friend.txt", "Pinned Best Friend");
fun resolve(context: Context): File = if (isDatabase) {
context.getDatabasePath(fileName)
} else {
File(context.filesDir, fileName)
}
companion object {
fun fromValue(value: Int): BridgeFileType? {
return entries.firstOrNull { it.value == value }
}
}
}

View File

@ -1,5 +0,0 @@
package me.rhunk.snapenhance.common.bridge.types
enum class FileActionType {
CREATE_AND_READ, READ, WRITE, DELETE, EXISTS
}

View File

@ -1,17 +0,0 @@
package me.rhunk.snapenhance.common.bridge.types
import android.os.ParcelFileDescriptor
import java.util.Locale
data class LocalePair(
val locale: String,
val content: ParcelFileDescriptor
) {
fun getLocale(): Locale {
if (locale.contains("_")) {
val split = locale.split("_")
return Locale(split[0], split[1])
}
return Locale(locale)
}
}

View File

@ -1,41 +1,22 @@
package me.rhunk.snapenhance.common.bridge.wrapper package me.rhunk.snapenhance.common.bridge.wrapper
import android.content.Context import android.content.Context
import android.os.ParcelFileDescriptor
import android.os.ParcelFileDescriptor.AutoCloseInputStream import android.os.ParcelFileDescriptor.AutoCloseInputStream
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.JsonParser import com.google.gson.JsonParser
import kotlinx.coroutines.CoroutineScope import me.rhunk.snapenhance.bridge.storage.FileHandleManager
import kotlinx.coroutines.Dispatchers import me.rhunk.snapenhance.common.bridge.FileHandleScope
import me.rhunk.snapenhance.common.bridge.types.LocalePair
import me.rhunk.snapenhance.common.logger.AbstractLogger import me.rhunk.snapenhance.common.logger.AbstractLogger
import me.rhunk.snapenhance.common.util.ktx.toParcelFileDescriptor
import java.util.Locale import java.util.Locale
class LocaleWrapper { class LocaleWrapper(
private val fileHandleManager: FileHandleManager
) {
companion object { companion object {
const val DEFAULT_LOCALE = "en_US" const val DEFAULT_LOCALE = "en_US"
fun fetchLocales(context: Context, locale: String = DEFAULT_LOCALE): List<LocalePair> {
val coroutineScope = CoroutineScope(Dispatchers.IO)
val locales = mutableListOf<LocalePair>().apply {
add(LocalePair(DEFAULT_LOCALE, context.resources.assets.open("lang/$DEFAULT_LOCALE.json").toParcelFileDescriptor(coroutineScope)))
}
if (locale == DEFAULT_LOCALE) return locales
val compatibleLocale = context.resources.assets.list("lang")?.firstOrNull { it.startsWith(locale) }?.substringBefore(".") ?: return locales
locales.add(
LocalePair(
compatibleLocale,
context.resources.assets.open("lang/$compatibleLocale.json").toParcelFileDescriptor(coroutineScope)
)
)
return locales
}
fun fetchAvailableLocales(context: Context): List<String> { fun fetchAvailableLocales(context: Context): List<String> {
return context.resources.assets.list("lang")?.map { it.substringBefore(".") }?.sorted() ?: listOf(DEFAULT_LOCALE) return context.resources.assets.list("lang")?.map { it.substringBefore(".") }?.sorted() ?: listOf(DEFAULT_LOCALE)
} }
@ -47,14 +28,23 @@ class LocaleWrapper {
lateinit var loadedLocale: Locale lateinit var loadedLocale: Locale
private fun load(localePair: LocalePair) { private fun load(locale: String, pfd: ParcelFileDescriptor) {
loadedLocale = localePair.getLocale() loadedLocale = if (locale.contains("_")) {
val split = locale.split("_")
Locale(split[0], split[1])
} else {
Locale(locale)
}
val translations = AutoCloseInputStream(localePair.content).use { val translations = AutoCloseInputStream(pfd).use {
JsonParser.parseReader(it.reader()).asJsonObject runCatching {
JsonParser.parseReader(it.reader()).asJsonObject
}.onFailure {
AbstractLogger.directError("Failed to parse locale file: ${it.message}", it)
}.getOrNull()
} }
if (translations == null || translations.isJsonNull) { if (translations == null || translations.isJsonNull) {
return throw IllegalStateException("Failed to parse $locale.json")
} }
fun scanObject(jsonObject: JsonObject, prefix: String = "") { fun scanObject(jsonObject: JsonObject, prefix: String = "") {
@ -71,22 +61,25 @@ class LocaleWrapper {
scanObject(translations) scanObject(translations)
} }
fun loadFromCallback(callback: (String) -> List<LocalePair>) { fun load() {
callback(userLocale).forEach { load(
load(it) DEFAULT_LOCALE,
fileHandleManager.getFileHandle(FileHandleScope.LOCALE.key, "$DEFAULT_LOCALE.json")?.open(ParcelFileDescriptor.MODE_READ_ONLY) ?: run {
throw IllegalStateException("Failed to load default locale")
}
)
if (userLocale != DEFAULT_LOCALE) {
fileHandleManager.getFileHandle(FileHandleScope.LOCALE.key, "$userLocale.json")?.open(ParcelFileDescriptor.MODE_READ_ONLY)?.let {
load(userLocale, it)
}
} }
} }
fun loadFromContext(context: Context) { fun reload(locale: String) {
fetchLocales(context, userLocale).forEach {
load(it)
}
}
fun reloadFromContext(context: Context, locale: String) {
userLocale = locale userLocale = locale
translationMap.clear() translationMap.clear()
loadFromContext(context) load()
} }
operator fun get(key: String) = translationMap[key] ?: key.also { AbstractLogger.directDebug("Missing translation for $key") } operator fun get(key: String) = translationMap[key] ?: key.also { AbstractLogger.directDebug("Missing translation for $key") }
@ -99,7 +92,7 @@ class LocaleWrapper {
} }
fun getCategory(key: String): LocaleWrapper { fun getCategory(key: String): LocaleWrapper {
return LocaleWrapper().apply { return LocaleWrapper(fileHandleManager).apply {
translationMap.putAll( translationMap.putAll(
this@LocaleWrapper.translationMap this@LocaleWrapper.translationMap
.filterKeys { it.startsWith("$key.") } .filterKeys { it.startsWith("$key.") }

View File

@ -1,11 +1,13 @@
package me.rhunk.snapenhance.common.bridge.wrapper package me.rhunk.snapenhance.common.bridge.wrapper
import android.content.ContentValues import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.google.gson.JsonObject import com.google.gson.JsonObject
import kotlinx.coroutines.* import kotlinx.coroutines.*
import me.rhunk.snapenhance.bridge.logger.LoggerInterface import me.rhunk.snapenhance.bridge.logger.LoggerInterface
import me.rhunk.snapenhance.common.bridge.InternalFileHandleType
import me.rhunk.snapenhance.common.data.StoryData import me.rhunk.snapenhance.common.data.StoryData
import me.rhunk.snapenhance.common.logger.AbstractLogger import me.rhunk.snapenhance.common.logger.AbstractLogger
import me.rhunk.snapenhance.common.util.SQLiteDatabaseHelper import me.rhunk.snapenhance.common.util.SQLiteDatabaseHelper
@ -61,6 +63,8 @@ class TrackerLog(
class LoggerWrapper( class LoggerWrapper(
val databaseFile: File val databaseFile: File
): LoggerInterface.Stub() { ): LoggerInterface.Stub() {
constructor(context: Context): this(File(context.getDatabasePath(InternalFileHandleType.MESSAGE_LOGGER.fileName).absolutePath))
private var _database: SQLiteDatabase? = null private var _database: SQLiteDatabase? = null
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
private val coroutineScope = CoroutineScope(Dispatchers.IO.limitedParallelism(1)) private val coroutineScope = CoroutineScope(Dispatchers.IO.limitedParallelism(1))

View File

@ -3,16 +3,19 @@ package me.rhunk.snapenhance.common.bridge.wrapper
import android.content.Context import android.content.Context
import com.google.gson.JsonParser import com.google.gson.JsonParser
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import me.rhunk.snapenhance.bridge.storage.FileHandleManager
import me.rhunk.snapenhance.common.BuildConfig import me.rhunk.snapenhance.common.BuildConfig
import me.rhunk.snapenhance.common.Constants import me.rhunk.snapenhance.common.Constants
import me.rhunk.snapenhance.common.bridge.FileLoaderWrapper import me.rhunk.snapenhance.common.bridge.InternalFileHandleType
import me.rhunk.snapenhance.common.bridge.types.BridgeFileType import me.rhunk.snapenhance.common.bridge.InternalFileWrapper
import me.rhunk.snapenhance.common.logger.AbstractLogger import me.rhunk.snapenhance.common.logger.AbstractLogger
import me.rhunk.snapenhance.mapper.AbstractClassMapper import me.rhunk.snapenhance.mapper.AbstractClassMapper
import me.rhunk.snapenhance.mapper.ClassMapper import me.rhunk.snapenhance.mapper.ClassMapper
import kotlin.reflect.KClass import kotlin.reflect.KClass
class MappingsWrapper : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteArray(Charsets.UTF_8)) { class MappingsWrapper(
fileHandleManager: FileHandleManager
): InternalFileWrapper(fileHandleManager, InternalFileHandleType.MAPPINGS, defaultValue = "{}") {
private lateinit var context: Context private lateinit var context: Context
private var mappingUniqueHash: Long = 0 private var mappingUniqueHash: Long = 0
var isMappingsLoaded = false var isMappingsLoaded = false
@ -26,7 +29,7 @@ class MappingsWrapper : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteAr
this.context = context this.context = context
mappingUniqueHash = getUniqueBuildId() mappingUniqueHash = getUniqueBuildId()
if (isFileExists()) { if (exists()) {
runCatching { runCatching {
loadCached() loadCached()
}.onFailure { }.onFailure {
@ -46,10 +49,10 @@ class MappingsWrapper : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteAr
fun isMappingsOutdated() = mappingUniqueHash != getUniqueBuildId() || isMappingsLoaded.not() fun isMappingsOutdated() = mappingUniqueHash != getUniqueBuildId() || isMappingsLoaded.not()
private fun loadCached() { private fun loadCached() {
if (!isFileExists()) { if (!exists()) {
throw Exception("Mappings file does not exist") throw Exception("Mappings file does not exist")
} }
val mappingsObject = JsonParser.parseString(read().toString(Charsets.UTF_8)).asJsonObject.also { val mappingsObject = JsonParser.parseString(readBytes().toString(Charsets.UTF_8)).asJsonObject.also {
mappingUniqueHash = it["unique_hash"].asLong mappingUniqueHash = it["unique_hash"].asLong
} }
@ -76,7 +79,7 @@ class MappingsWrapper : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteAr
val result = classMapper.run().apply { val result = classMapper.run().apply {
addProperty("unique_hash", mappingUniqueHash) addProperty("unique_hash", mappingUniqueHash)
} }
write(result.toString().toByteArray()) writeBytes(result.toString().toByteArray())
} }
} }

View File

@ -5,20 +5,22 @@ import com.google.gson.Gson
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.google.gson.JsonObject import com.google.gson.JsonObject
import me.rhunk.snapenhance.bridge.ConfigStateListener import me.rhunk.snapenhance.bridge.ConfigStateListener
import me.rhunk.snapenhance.common.bridge.FileLoaderWrapper import me.rhunk.snapenhance.bridge.storage.FileHandleManager
import me.rhunk.snapenhance.common.bridge.types.BridgeFileType import me.rhunk.snapenhance.common.bridge.InternalFileHandleType
import me.rhunk.snapenhance.common.bridge.InternalFileWrapper
import me.rhunk.snapenhance.common.bridge.wrapper.LocaleWrapper import me.rhunk.snapenhance.common.bridge.wrapper.LocaleWrapper
import me.rhunk.snapenhance.common.config.impl.RootConfig import me.rhunk.snapenhance.common.config.impl.RootConfig
import me.rhunk.snapenhance.common.logger.AbstractLogger import me.rhunk.snapenhance.common.logger.AbstractLogger
import kotlin.properties.Delegates import kotlin.properties.Delegates
class ModConfig( class ModConfig(
private val context: Context private val context: Context,
fileHandleManager: FileHandleManager
) { ) {
private val fileWrapper = InternalFileWrapper(fileHandleManager, InternalFileHandleType.CONFIG, "{}")
var locale: String = LocaleWrapper.DEFAULT_LOCALE var locale: String = LocaleWrapper.DEFAULT_LOCALE
private val gson: Gson = GsonBuilder().setPrettyPrinting().create() private val gson: Gson = GsonBuilder().setPrettyPrinting().create()
private val file = FileLoaderWrapper(BridgeFileType.CONFIG, "{}".toByteArray(Charsets.UTF_8))
var wasPresent by Delegates.notNull<Boolean>() var wasPresent by Delegates.notNull<Boolean>()
/* Used to notify the bridge client about config changes */ /* Used to notify the bridge client about config changes */
@ -30,9 +32,9 @@ class ModConfig(
private fun createRootConfig() = RootConfig().apply { lateInit(context) } private fun createRootConfig() = RootConfig().apply { lateInit(context) }
private fun load() { fun load() {
root = createRootConfig() root = createRootConfig()
wasPresent = file.isFileExists() wasPresent = fileWrapper.exists()
if (!wasPresent) { if (!wasPresent) {
writeConfig() writeConfig()
return return
@ -46,7 +48,7 @@ class ModConfig(
} }
private fun loadConfig() { private fun loadConfig() {
val configFileContent = file.read() val configFileContent = fileWrapper.readBytes()
val configObject = gson.fromJson(configFileContent.toString(Charsets.UTF_8), JsonObject::class.java) val configObject = gson.fromJson(configFileContent.toString(Charsets.UTF_8), JsonObject::class.java)
locale = configObject.get("_locale")?.asString ?: LocaleWrapper.DEFAULT_LOCALE locale = configObject.get("_locale")?.asString ?: LocaleWrapper.DEFAULT_LOCALE
root.fromJson(configObject) root.fromJson(configObject)
@ -99,8 +101,8 @@ class ModConfig(
} }
} }
val oldConfig = runCatching { file.read().toString(Charsets.UTF_8) }.getOrNull() val oldConfig = runCatching { fileWrapper.readBytes().toString(Charsets.UTF_8) }.getOrNull()
file.write(exportToString().toByteArray(Charsets.UTF_8)) fileWrapper.writeBytes(exportToString().toByteArray(Charsets.UTF_8))
configStateListener?.also { configStateListener?.also {
runCatching { runCatching {
@ -125,14 +127,4 @@ class ModConfig(
root.fromJson(configObject) root.fromJson(configObject)
writeConfig() writeConfig()
} }
fun loadFromContext(context: Context) {
file.loadFromContext(context)
load()
}
fun loadFromCallback(callback: (FileLoaderWrapper) -> Unit) {
callback(file)
load()
}
} }

View File

@ -19,7 +19,6 @@ import me.rhunk.snapenhance.common.bridge.wrapper.MappingsWrapper
import me.rhunk.snapenhance.common.config.ModConfig import me.rhunk.snapenhance.common.config.ModConfig
import me.rhunk.snapenhance.core.action.ActionManager import me.rhunk.snapenhance.core.action.ActionManager
import me.rhunk.snapenhance.core.bridge.BridgeClient import me.rhunk.snapenhance.core.bridge.BridgeClient
import me.rhunk.snapenhance.core.bridge.loadFromBridge
import me.rhunk.snapenhance.core.database.DatabaseAccess import me.rhunk.snapenhance.core.database.DatabaseAccess
import me.rhunk.snapenhance.core.event.EventBus import me.rhunk.snapenhance.core.event.EventBus
import me.rhunk.snapenhance.core.event.EventDispatcher import me.rhunk.snapenhance.core.event.EventDispatcher
@ -48,15 +47,15 @@ class ModContext(
val resources: Resources get() = androidContext.resources val resources: Resources get() = androidContext.resources
val gson: Gson = GsonBuilder().create() val gson: Gson = GsonBuilder().create()
private val _config = ModConfig(androidContext) private val _config by lazy { ModConfig(androidContext, bridgeClient.getFileHandlerManager()) }
val config by _config::root val config get() = _config.root
val log by lazy { CoreLogger(this.bridgeClient) } val log by lazy { CoreLogger(this.bridgeClient) }
val translation = LocaleWrapper() val translation by lazy { LocaleWrapper(bridgeClient.getFileHandlerManager()) }
val httpServer = HttpServer() val httpServer = HttpServer()
val messageSender = MessageSender(this) val messageSender = MessageSender(this)
val features = FeatureManager(this) val features = FeatureManager(this)
val mappings = MappingsWrapper() val mappings by lazy { MappingsWrapper(bridgeClient.getFileHandlerManager()) }
val actionManager = ActionManager(this) val actionManager = ActionManager(this)
val database = DatabaseAccess(this) val database = DatabaseAccess(this)
val event = EventBus(this) val event = EventBus(this)
@ -144,9 +143,7 @@ class ModContext(
fun reloadConfig() { fun reloadConfig() {
log.verbose("reloading config") log.verbose("reloading config")
_config.loadFromCallback { file -> _config.load()
file.loadFromBridge(bridgeClient)
}
reloadNativeConfig() reloadNativeConfig()
} }

View File

@ -19,7 +19,6 @@ import me.rhunk.snapenhance.common.data.MessagingFriendInfo
import me.rhunk.snapenhance.common.data.MessagingGroupInfo import me.rhunk.snapenhance.common.data.MessagingGroupInfo
import me.rhunk.snapenhance.common.util.toSerialized import me.rhunk.snapenhance.common.util.toSerialized
import me.rhunk.snapenhance.core.bridge.BridgeClient import me.rhunk.snapenhance.core.bridge.BridgeClient
import me.rhunk.snapenhance.core.bridge.loadFromBridge
import me.rhunk.snapenhance.core.data.SnapClassCache import me.rhunk.snapenhance.core.data.SnapClassCache
import me.rhunk.snapenhance.core.event.events.impl.NativeUnaryCallEvent import me.rhunk.snapenhance.core.event.events.impl.NativeUnaryCallEvent
import me.rhunk.snapenhance.core.event.events.impl.SnapWidgetBroadcastReceiveEvent import me.rhunk.snapenhance.core.event.events.impl.SnapWidgetBroadcastReceiveEvent
@ -148,12 +147,9 @@ class SnapEnhance {
log.error("Failed to sync remote", it) log.error("Failed to sync remote", it)
} }
translation.userLocale = getConfigLocale() translation.userLocale = getConfigLocale()
translation.loadFromCallback { locale -> translation.load()
bridgeClient.fetchLocales(locale)
}
} }
mappings.loadFromBridge(bridgeClient)
mappings.init(androidContext) mappings.init(androidContext)
database.init() database.init()
eventDispatcher.init() eventDispatcher.init()

View File

@ -18,11 +18,8 @@ import me.rhunk.snapenhance.bridge.logger.LoggerInterface
import me.rhunk.snapenhance.bridge.logger.TrackerInterface import me.rhunk.snapenhance.bridge.logger.TrackerInterface
import me.rhunk.snapenhance.bridge.scripting.IScripting import me.rhunk.snapenhance.bridge.scripting.IScripting
import me.rhunk.snapenhance.bridge.snapclient.MessagingBridge import me.rhunk.snapenhance.bridge.snapclient.MessagingBridge
import me.rhunk.snapenhance.bridge.storage.FileHandleManager
import me.rhunk.snapenhance.common.Constants import me.rhunk.snapenhance.common.Constants
import me.rhunk.snapenhance.common.bridge.FileLoaderWrapper
import me.rhunk.snapenhance.common.bridge.types.BridgeFileType
import me.rhunk.snapenhance.common.bridge.types.FileActionType
import me.rhunk.snapenhance.common.bridge.types.LocalePair
import me.rhunk.snapenhance.common.data.MessagingFriendInfo import me.rhunk.snapenhance.common.data.MessagingFriendInfo
import me.rhunk.snapenhance.common.data.MessagingGroupInfo import me.rhunk.snapenhance.common.data.MessagingGroupInfo
import me.rhunk.snapenhance.common.data.MessagingRuleType import me.rhunk.snapenhance.common.data.MessagingRuleType
@ -34,14 +31,6 @@ import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.system.exitProcess import kotlin.system.exitProcess
fun FileLoaderWrapper.loadFromBridge(bridgeClient: BridgeClient) {
isFileExists = { bridgeClient.isFileExists(fileType) }
read = { bridgeClient.createAndReadFile(fileType, defaultContent) }
write = { bridgeClient.writeFile(fileType, it) }
delete = { bridgeClient.deleteFile(fileType) }
}
class BridgeClient( class BridgeClient(
private val context: ModContext private val context: ModContext
): ServiceConnection { ): ServiceConnection {
@ -125,39 +114,6 @@ class BridgeClient(
} }
} }
//TODO: use interfaces instead of direct file access
fun createAndReadFile(
fileType: BridgeFileType,
defaultContent: ByteArray
): ByteArray = safeServiceCall {
service.fileOperation(FileActionType.CREATE_AND_READ.ordinal, fileType.value, defaultContent)
}
fun readFile(fileType: BridgeFileType): ByteArray = safeServiceCall { service.fileOperation(FileActionType.READ.ordinal, fileType.value, null) }
fun writeFile(
fileType: BridgeFileType,
content: ByteArray?
): ByteArray = safeServiceCall {
service.fileOperation(FileActionType.WRITE.ordinal, fileType.value, content)
}
fun deleteFile(fileType: BridgeFileType) {
safeServiceCall {
service.fileOperation(FileActionType.DELETE.ordinal, fileType.value, null)
}
}
fun isFileExists(fileType: BridgeFileType) = safeServiceCall {
service.fileOperation(FileActionType.EXISTS.ordinal, fileType.value, null).isNotEmpty()
}
fun fetchLocales(userLocale: String) = safeServiceCall {
service.fetchLocales(userLocale).map {
LocalePair(it.key, it.value)
}
}
fun getApplicationApkPath(): String = safeServiceCall { service.applicationApkPath } fun getApplicationApkPath(): String = safeServiceCall { service.applicationApkPath }
fun enqueueDownload(intent: Intent, callback: DownloadCallback) = safeServiceCall { fun enqueueDownload(intent: Intent, callback: DownloadCallback) = safeServiceCall {
@ -215,6 +171,8 @@ class BridgeClient(
fun getAccountStorage(): AccountStorage = safeServiceCall { service.accountStorage } fun getAccountStorage(): AccountStorage = safeServiceCall { service.accountStorage }
fun getFileHandlerManager(): FileHandleManager = safeServiceCall { service.fileHandleManager }
fun registerMessagingBridge(bridge: MessagingBridge) = safeServiceCall { service.registerMessagingBridge(bridge) } fun registerMessagingBridge(bridge: MessagingBridge) = safeServiceCall { service.registerMessagingBridge(bridge) }
fun openSettingsOverlay() = safeServiceCall { service.openSettingsOverlay() } fun openSettingsOverlay() = safeServiceCall { service.openSettingsOverlay() }

View File

@ -1,32 +1,38 @@
package me.rhunk.snapenhance.core.features package me.rhunk.snapenhance.core.features
import me.rhunk.snapenhance.common.bridge.types.BridgeFileType import me.rhunk.snapenhance.common.bridge.FileHandleScope
import me.rhunk.snapenhance.common.bridge.InternalFileHandleType
import me.rhunk.snapenhance.common.bridge.toWrapper
import java.io.BufferedReader import java.io.BufferedReader
import java.io.ByteArrayInputStream
import java.io.InputStreamReader import java.io.InputStreamReader
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
abstract class BridgeFileFeature(name: String, private val bridgeFileType: BridgeFileType, loadParams: Int) : Feature(name, loadParams) { abstract class BridgeFileFeature(name: String, private val bridgeFileType: InternalFileHandleType, loadParams: Int) : Feature(name, loadParams) {
private val fileLines = mutableListOf<String>() private val fileLines = mutableListOf<String>()
private val fileWrapper by lazy { context.bridgeClient.getFileHandlerManager().getFileHandle(FileHandleScope.INTERNAL.key, bridgeFileType.key)!!.toWrapper() }
protected fun readFile() { protected fun readFile() {
val temporaryLines = mutableListOf<String>() val temporaryLines = mutableListOf<String>()
val fileData: ByteArray = context.bridgeClient.createAndReadFile(bridgeFileType, ByteArray(0)) fileWrapper.inputStream { stream ->
with(BufferedReader(InputStreamReader(ByteArrayInputStream(fileData), StandardCharsets.UTF_8))) { with(BufferedReader(InputStreamReader(stream, StandardCharsets.UTF_8))) {
var line = "" var line = ""
while (readLine()?.also { line = it } != null) temporaryLines.add(line) while (readLine()?.also { line = it } != null) temporaryLines.add(line)
close() close()
}
} }
fileLines.clear() fileLines.clear()
fileLines.addAll(temporaryLines) fileLines.addAll(temporaryLines)
} }
private fun updateFile() { private fun updateFile() {
val sb = StringBuilder() fileWrapper.outputStream { stream ->
fileLines.forEach { fileLines.forEach {
sb.append(it).append("\n") stream.write(it.toByteArray())
stream.write("\n".toByteArray())
stream.flush()
}
} }
context.bridgeClient.writeFile(bridgeFileType, sb.toString().toByteArray(Charsets.UTF_8))
} }
protected fun exists(line: String) = fileLines.contains(line) protected fun exists(line: String) = fileLines.contains(line)

View File

@ -7,7 +7,7 @@ import com.google.gson.JsonObject
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.rhunk.snapenhance.common.bridge.types.BridgeFileType import me.rhunk.snapenhance.common.bridge.InternalFileHandleType
import me.rhunk.snapenhance.common.util.protobuf.ProtoReader import me.rhunk.snapenhance.common.util.protobuf.ProtoReader
import me.rhunk.snapenhance.core.event.events.impl.NetworkApiRequestEvent import me.rhunk.snapenhance.core.event.events.impl.NetworkApiRequestEvent
import me.rhunk.snapenhance.core.event.events.impl.UnaryCallEvent import me.rhunk.snapenhance.core.event.events.impl.UnaryCallEvent
@ -18,7 +18,7 @@ import java.io.InputStreamReader
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.util.UUID import java.util.UUID
class BestFriendPinning: BridgeFileFeature("Best Friend Pinning", BridgeFileType.PINNED_BEST_FRIEND, loadParams = FeatureLoadParams.INIT_SYNC) { class BestFriendPinning: BridgeFileFeature("Best Friend Pinning", InternalFileHandleType.PINNED_BEST_FRIEND, loadParams = FeatureLoadParams.INIT_SYNC) {
private fun updatePinnedBestFriendStatus() { private fun updatePinnedBestFriendStatus() {
lines().firstOrNull()?.trim()?.let { lines().firstOrNull()?.trim()?.let {
context.database.updatePinnedBestFriendStatus(it.substring(0, 36), "number_one_bf_for_two_months") context.database.updatePinnedBestFriendStatus(it.substring(0, 36), "number_one_bf_for_two_months")

View File

@ -2,7 +2,7 @@ package me.rhunk.snapenhance.core.features.impl.global
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Switch import android.widget.Switch
import me.rhunk.snapenhance.common.bridge.types.BridgeFileType import me.rhunk.snapenhance.common.bridge.InternalFileHandleType
import me.rhunk.snapenhance.core.event.events.impl.LayoutInflateEvent import me.rhunk.snapenhance.core.event.events.impl.LayoutInflateEvent
import me.rhunk.snapenhance.core.features.BridgeFileFeature import me.rhunk.snapenhance.core.features.BridgeFileFeature
import me.rhunk.snapenhance.core.features.FeatureLoadParams import me.rhunk.snapenhance.core.features.FeatureLoadParams
@ -12,7 +12,7 @@ import me.rhunk.snapenhance.core.util.ktx.getLayoutId
class SuspendLocationUpdates : BridgeFileFeature( class SuspendLocationUpdates : BridgeFileFeature(
"Suspend Location Updates", "Suspend Location Updates",
loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC, bridgeFileType = BridgeFileType.SUSPEND_LOCATION_STATE) { loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC, bridgeFileType = InternalFileHandleType.SUSPEND_LOCATION_STATE) {
fun isSuspended() = exists("true") fun isSuspended() = exists("true")
private fun setSuspended(suspended: Boolean) = setState("true", suspended) private fun setSuspended(suspended: Boolean) = setState("true", suspended)