manager: Add SuSFS configuration backup and restore feature

- Optimize susfs self-boot scripts
- Solve some invalid issues where startup duration does not match or is too fast.

Signed-off-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
This commit is contained in:
ShirkNeko
2025-06-28 23:19:18 +08:00
parent c49a66d1af
commit 8399f14fad
11 changed files with 589 additions and 68 deletions

View File

@@ -1,6 +1,8 @@
package com.sukisu.ultra.ui.screen
import android.annotation.SuppressLint
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -80,6 +82,8 @@ import com.sukisu.ultra.ui.theme.CardConfig
import com.sukisu.ultra.ui.util.SuSFSManager
import com.sukisu.ultra.ui.util.SuSFSManager.isSusVersion_1_5_8
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.*
/**
* 标签页枚举类
@@ -123,8 +127,6 @@ fun SuSFSConfigScreen(
var isLoading by remember { mutableStateOf(false) }
var showConfirmReset by remember { mutableStateOf(false) }
var autoStartEnabled by remember { mutableStateOf(false) }
var lastAppliedValue by remember { mutableStateOf("") }
var lastAppliedBuildTime by remember { mutableStateOf("") }
var executeInPostFsData by remember { mutableStateOf(false) }
// 槽位信息相关状态
@@ -165,6 +167,13 @@ fun SuSFSConfigScreen(
var showResetUmountsDialog by remember { mutableStateOf(false) }
var showResetKstatDialog by remember { mutableStateOf(false) }
// 备份还原相关状态
var showBackupDialog by remember { mutableStateOf(false) }
var showRestoreDialog by remember { mutableStateOf(false) }
var showRestoreConfirmDialog by remember { mutableStateOf(false) }
var selectedBackupFile by remember { mutableStateOf<String?>(null) }
var backupInfo by remember { mutableStateOf<SuSFSManager.BackupData?>(null) }
val allTabs = SuSFSTab.getAllTabs(isSusVersion_1_5_8())
// 实时判断是否可以启用开机自启动
@@ -174,6 +183,65 @@ fun SuSFSConfigScreen(
}
}
// 文件选择器
val backupFileLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.CreateDocument("application/json")
) { uri ->
uri?.let { fileUri ->
val fileName = SuSFSManager.getRecommendedBackupPath(context)
coroutineScope.launch {
isLoading = true
val success = SuSFSManager.createBackup(context, fileName)
if (success) {
// 复制到用户选择的位置
try {
context.contentResolver.openOutputStream(fileUri)?.use { outputStream ->
java.io.File(fileName).inputStream().use { inputStream ->
inputStream.copyTo(outputStream)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
isLoading = false
showBackupDialog = false
}
}
}
val restoreFileLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocument()
) { uri ->
uri?.let { fileUri ->
coroutineScope.launch {
try {
// 复制到临时文件
val tempFile = java.io.File(context.cacheDir, "temp_restore.susfs_backup")
context.contentResolver.openInputStream(fileUri)?.use { inputStream ->
tempFile.outputStream().use { outputStream ->
inputStream.copyTo(outputStream)
}
}
// 验证备份文件
val backup = SuSFSManager.validateBackupFile(tempFile.absolutePath)
if (backup != null) {
selectedBackupFile = tempFile.absolutePath
backupInfo = backup
showRestoreConfirmDialog = true
} else {
// 显示错误消息
}
tempFile.deleteOnExit()
} catch (e: Exception) {
e.printStackTrace()
}
showRestoreDialog = false
}
}
}
// 加载启用功能状态
fun loadEnabledFeatures() {
coroutineScope.launch {
@@ -198,8 +266,6 @@ fun SuSFSConfigScreen(
unameValue = SuSFSManager.getUnameValue(context)
buildTimeValue = SuSFSManager.getBuildTimeValue(context)
autoStartEnabled = SuSFSManager.isAutoStartEnabled(context)
lastAppliedValue = SuSFSManager.getLastAppliedValue(context)
lastAppliedBuildTime = SuSFSManager.getLastAppliedBuildTime(context)
executeInPostFsData = SuSFSManager.getExecuteInPostFsData(context)
susPaths = SuSFSManager.getSusPaths(context)
susMounts = SuSFSManager.getSusMounts(context)
@@ -228,6 +294,183 @@ fun SuSFSConfigScreen(
}
}
// 备份对话框
if (showBackupDialog) {
AlertDialog(
onDismissRequest = { showBackupDialog = false },
title = {
Text(
text = stringResource(R.string.susfs_backup_title),
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold
)
},
text = {
Text(stringResource(R.string.susfs_backup_description))
},
confirmButton = {
Button(
onClick = {
val dateFormat = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault())
val timestamp = dateFormat.format(Date())
backupFileLauncher.launch("SuSFS_Config_$timestamp.susfs_backup")
},
enabled = !isLoading,
shape = RoundedCornerShape(8.dp)
) {
Text(stringResource(R.string.susfs_backup_create))
}
},
dismissButton = {
TextButton(
onClick = { showBackupDialog = false },
shape = RoundedCornerShape(8.dp)
) {
Text(stringResource(R.string.cancel))
}
},
shape = RoundedCornerShape(12.dp)
)
}
// 还原对话框
if (showRestoreDialog) {
AlertDialog(
onDismissRequest = { showRestoreDialog = false },
title = {
Text(
text = stringResource(R.string.susfs_restore_title),
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold
)
},
text = {
Text(stringResource(R.string.susfs_restore_description))
},
confirmButton = {
Button(
onClick = {
restoreFileLauncher.launch(arrayOf("application/json", "*/*"))
},
enabled = !isLoading,
shape = RoundedCornerShape(8.dp)
) {
Text(stringResource(R.string.susfs_restore_select_file))
}
},
dismissButton = {
TextButton(
onClick = { showRestoreDialog = false },
shape = RoundedCornerShape(8.dp)
) {
Text(stringResource(R.string.cancel))
}
},
shape = RoundedCornerShape(12.dp)
)
}
// 还原确认对话框
if (showRestoreConfirmDialog && backupInfo != null) {
AlertDialog(
onDismissRequest = {
showRestoreConfirmDialog = false
selectedBackupFile = null
backupInfo = null
},
title = {
Text(
text = stringResource(R.string.susfs_restore_confirm_title),
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold
)
},
text = {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(stringResource(R.string.susfs_restore_confirm_description))
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f)
),
shape = RoundedCornerShape(8.dp)
) {
Column(
modifier = Modifier.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
Text(
text = stringResource(R.string.susfs_backup_info_date,
dateFormat.format(Date(backupInfo!!.timestamp))),
style = MaterialTheme.typography.bodyMedium
)
Text(
text = stringResource(R.string.susfs_backup_info_device, backupInfo!!.deviceInfo),
style = MaterialTheme.typography.bodyMedium
)
Text(
text = stringResource(R.string.susfs_backup_info_version, backupInfo!!.version),
style = MaterialTheme.typography.bodyMedium
)
}
}
}
},
confirmButton = {
Button(
onClick = {
selectedBackupFile?.let { filePath ->
coroutineScope.launch {
isLoading = true
val success = SuSFSManager.restoreFromBackup(context, filePath)
if (success) {
// 重新加载所有配置
unameValue = SuSFSManager.getUnameValue(context)
buildTimeValue = SuSFSManager.getBuildTimeValue(context)
autoStartEnabled = SuSFSManager.isAutoStartEnabled(context)
executeInPostFsData = SuSFSManager.getExecuteInPostFsData(context)
susPaths = SuSFSManager.getSusPaths(context)
susMounts = SuSFSManager.getSusMounts(context)
tryUmounts = SuSFSManager.getTryUmounts(context)
androidDataPath = SuSFSManager.getAndroidDataPath(context)
sdcardPath = SuSFSManager.getSdcardPath(context)
kstatConfigs = SuSFSManager.getKstatConfigs(context)
addKstatPaths = SuSFSManager.getAddKstatPaths(context)
hideSusMountsForAllProcs = SuSFSManager.getHideSusMountsForAllProcs(context)
}
isLoading = false
showRestoreConfirmDialog = false
selectedBackupFile = null
backupInfo = null
}
}
},
enabled = !isLoading,
shape = RoundedCornerShape(8.dp)
) {
Text(stringResource(R.string.susfs_restore_confirm))
}
},
dismissButton = {
TextButton(
onClick = {
showRestoreConfirmDialog = false
selectedBackupFile = null
backupInfo = null
},
shape = RoundedCornerShape(8.dp)
) {
Text(stringResource(R.string.cancel))
}
},
shape = RoundedCornerShape(12.dp)
)
}
// 槽位信息对话框
SlotInfoDialog(
showDialog = showSlotInfoDialog,
@@ -366,8 +609,6 @@ fun SuSFSConfigScreen(
if (SuSFSManager.resetToDefault(context)) {
unameValue = "default"
buildTimeValue = "default"
lastAppliedValue = "default"
lastAppliedBuildTime = "default"
autoStartEnabled = false
}
isLoading = false
@@ -530,8 +771,6 @@ fun SuSFSConfigScreen(
val finalBuildTimeValue = buildTimeValue.trim().ifBlank { "default" }
val success = SuSFSManager.setUname(context, finalUnameValue, finalBuildTimeValue)
if (success) {
lastAppliedValue = finalUnameValue
lastAppliedBuildTime = finalBuildTimeValue
SuSFSManager.saveExecuteInPostFsData(context, executeInPostFsData)
}
isLoading = false
@@ -780,7 +1019,9 @@ fun SuSFSConfigScreen(
}
},
onShowSlotInfo = { showSlotInfoDialog = true },
context = context
context = context,
onShowBackupDialog = { showBackupDialog = true },
onShowRestoreDialog = { showRestoreDialog = true }
)
}
SuSFSTab.SUS_PATHS -> {
@@ -939,7 +1180,9 @@ private fun BasicSettingsContent(
isLoading: Boolean,
onAutoStartToggle: (Boolean) -> Unit,
onShowSlotInfo: () -> Unit,
context: android.content.Context
context: android.content.Context,
onShowBackupDialog: () -> Unit,
onShowRestoreDialog: () -> Unit
) {
var scriptLocationExpanded by remember { mutableStateOf(false) }
@@ -1204,6 +1447,51 @@ private fun BasicSettingsContent(
}
}
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
// 备份按钮
OutlinedButton(
onClick = onShowBackupDialog,
enabled = !isLoading,
shape = RoundedCornerShape(8.dp),
modifier = Modifier
.weight(1f)
.height(40.dp)
) {
Icon(
imageVector = Icons.Default.Backup,
contentDescription = null,
modifier = Modifier.size(16.dp)
)
Spacer(modifier = Modifier.width(6.dp))
Text(
stringResource(R.string.susfs_backup_title),
fontWeight = FontWeight.Medium
)
}
// 还原按钮
OutlinedButton(
onClick = onShowRestoreDialog,
enabled = !isLoading,
shape = RoundedCornerShape(8.dp),
modifier = Modifier
.weight(1f)
.height(40.dp)
) {
Icon(
imageVector = Icons.Default.Restore,
contentDescription = null,
modifier = Modifier.size(16.dp)
)
Spacer(modifier = Modifier.width(6.dp))
Text(
stringResource(R.string.susfs_restore_title),
fontWeight = FontWeight.Medium
)
}
}
}
}

View File

@@ -16,6 +16,9 @@ import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import androidx.core.content.edit
import org.json.JSONObject
import java.text.SimpleDateFormat
import java.util.*
/**
* SuSFS 配置管理器
@@ -44,6 +47,7 @@ object SuSFSManager {
private const val MODULE_ID = "susfs_manager"
private const val MODULE_PATH = "/data/adb/modules/$MODULE_ID"
private const val MIN_VERSION_FOR_HIDE_MOUNT = "1.5.8"
private const val BACKUP_FILE_EXTENSION = ".susfs_backup"
data class SlotInfo(val slotName: String, val uname: String, val buildTime: String)
data class CommandResult(val isSuccess: Boolean, val output: String, val errorOutput: String = "")
@@ -54,6 +58,60 @@ object SuSFSManager {
val canConfigure: Boolean = false
)
/**
* 备份数据类
*/
data class BackupData(
val version: String,
val timestamp: Long,
val deviceInfo: String,
val configurations: Map<String, Any>
) {
fun toJson(): String {
val jsonObject = JSONObject().apply {
put("version", version)
put("timestamp", timestamp)
put("deviceInfo", deviceInfo)
put("configurations", JSONObject(configurations))
}
return jsonObject.toString(2)
}
companion object {
fun fromJson(jsonString: String): BackupData? {
return try {
val jsonObject = JSONObject(jsonString)
val configurationsJson = jsonObject.getJSONObject("configurations")
val configurations = mutableMapOf<String, Any>()
configurationsJson.keys().forEach { key ->
val value = configurationsJson.get(key)
configurations[key] = when (value) {
is org.json.JSONArray -> {
val set = mutableSetOf<String>()
for (i in 0 until value.length()) {
set.add(value.getString(i))
}
set
}
else -> value
}
}
BackupData(
version = jsonObject.getString("version"),
timestamp = jsonObject.getLong("timestamp"),
deviceInfo = jsonObject.getString("deviceInfo"),
configurations = configurations
)
} catch (e: Exception) {
e.printStackTrace()
null
}
}
}
}
/**
* 模块配置数据类
*/
@@ -180,9 +238,6 @@ object SuSFSManager {
fun getBuildTimeValue(context: Context): String =
getPrefs(context).getString(KEY_BUILD_TIME_VALUE, DEFAULT_BUILD_TIME) ?: DEFAULT_BUILD_TIME
fun getLastAppliedValue(context: Context): String = getUnameValue(context)
fun getLastAppliedBuildTime(context: Context): String = getBuildTimeValue(context)
fun setAutoStartEnabled(context: Context, enabled: Boolean) =
getPrefs(context).edit { putBoolean(KEY_AUTO_START_ENABLED, enabled) }
@@ -261,6 +316,154 @@ object SuSFSManager {
fun getSdcardPath(context: Context): String =
getPrefs(context).getString(KEY_SDCARD_PATH, "/sdcard") ?: "/sdcard"
// 获取所有配置的Map
private fun getAllConfigurations(context: Context): Map<String, Any> {
return mapOf(
KEY_UNAME_VALUE to getUnameValue(context),
KEY_BUILD_TIME_VALUE to getBuildTimeValue(context),
KEY_AUTO_START_ENABLED to isAutoStartEnabled(context),
KEY_SUS_PATHS to getSusPaths(context),
KEY_SUS_MOUNTS to getSusMounts(context),
KEY_TRY_UMOUNTS to getTryUmounts(context),
KEY_ANDROID_DATA_PATH to getAndroidDataPath(context),
KEY_SDCARD_PATH to getSdcardPath(context),
KEY_ENABLE_LOG to getEnableLogState(context),
KEY_EXECUTE_IN_POST_FS_DATA to getExecuteInPostFsData(context),
KEY_KSTAT_CONFIGS to getKstatConfigs(context),
KEY_ADD_KSTAT_PATHS to getAddKstatPaths(context),
KEY_HIDE_SUS_MOUNTS_FOR_ALL_PROCS to getHideSusMountsForAllProcs(context)
)
}
//生成备份文件名
private fun generateBackupFileName(): String {
val dateFormat = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault())
val timestamp = dateFormat.format(Date())
return "SuSFS_Config_$timestamp$BACKUP_FILE_EXTENSION"
}
// 获取设备信息
private fun getDeviceInfo(): String {
return try {
"${android.os.Build.MANUFACTURER} ${android.os.Build.MODEL} (${android.os.Build.VERSION.RELEASE})"
} catch (_: Exception) {
"Unknown Device"
}
}
// 创建配置备份
suspend fun createBackup(context: Context, backupFilePath: String): Boolean = withContext(Dispatchers.IO) {
try {
val configurations = getAllConfigurations(context)
val backupData = BackupData(
version = getSuSFSVersion(),
timestamp = System.currentTimeMillis(),
deviceInfo = getDeviceInfo(),
configurations = configurations
)
val backupFile = File(backupFilePath)
backupFile.parentFile?.mkdirs()
backupFile.writeText(backupData.toJson())
showToast(context, context.getString(R.string.susfs_backup_success, backupFile.name))
true
} catch (e: Exception) {
e.printStackTrace()
showToast(context, context.getString(R.string.susfs_backup_failed, e.message ?: "Unknown error"))
false
}
}
//从备份文件还原配置
suspend fun restoreFromBackup(context: Context, backupFilePath: String): Boolean = withContext(Dispatchers.IO) {
try {
val backupFile = File(backupFilePath)
if (!backupFile.exists()) {
showToast(context, context.getString(R.string.susfs_backup_file_not_found))
return@withContext false
}
val backupContent = backupFile.readText()
val backupData = BackupData.fromJson(backupContent)
if (backupData == null) {
showToast(context, context.getString(R.string.susfs_backup_invalid_format))
return@withContext false
}
// 检查备份版本兼容性
if (backupData.version != getSuSFSVersion()) {
showToast(context, context.getString(R.string.susfs_backup_version_mismatch))
}
// 还原所有配置
restoreConfigurations(context, backupData.configurations)
// 如果自启动已启用,更新模块
if (isAutoStartEnabled(context)) {
updateMagiskModule(context)
}
val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
val backupDate = dateFormat.format(Date(backupData.timestamp))
showToast(context, context.getString(R.string.susfs_restore_success, backupDate, backupData.deviceInfo))
true
} catch (e: Exception) {
e.printStackTrace()
showToast(context, context.getString(R.string.susfs_restore_failed, e.message ?: "Unknown error"))
false
}
}
// 还原配置到SharedPreferences
private fun restoreConfigurations(context: Context, configurations: Map<String, Any>) {
val prefs = getPrefs(context)
prefs.edit {
configurations.forEach { (key, value) ->
when (value) {
is String -> putString(key, value)
is Boolean -> putBoolean(key, value)
is Set<*> -> {
@Suppress("UNCHECKED_CAST")
putStringSet(key, value as Set<String>)
}
is Int -> putInt(key, value)
is Long -> putLong(key, value)
is Float -> putFloat(key, value)
}
}
}
}
// 验证备份文件
suspend fun validateBackupFile(backupFilePath: String): BackupData? = withContext(Dispatchers.IO) {
try {
val backupFile = File(backupFilePath)
if (!backupFile.exists()) {
return@withContext null
}
val backupContent = backupFile.readText()
BackupData.fromJson(backupContent)
} catch (e: Exception) {
e.printStackTrace()
null
}
}
//获取备份文件路径
fun getRecommendedBackupPath(context: Context): String {
val documentsDir = File(context.getExternalFilesDir(null), "SuSFS_Backups")
if (!documentsDir.exists()) {
documentsDir.mkdirs()
}
return File(documentsDir, generateBackupFileName()).absolutePath
}
// 槽位信息获取
suspend fun getCurrentSlotInfo(): List<SlotInfo> = withContext(Dispatchers.IO) {
try {

View File

@@ -53,6 +53,7 @@ object ScriptGenerator {
/**
* 生成service.sh脚本内容
*/
@SuppressLint("SdCardPath")
private fun generateServiceScript(config: SuSFSManager.ModuleConfig): String {
return buildString {
appendLine("#!/system/bin/sh")
@@ -67,6 +68,9 @@ object ScriptGenerator {
if (shouldConfigureInService(config)) {
// 添加SUS路径 (仅在不支持隐藏挂载时)
if (!config.support158 && config.susPaths.isNotEmpty()) {
appendLine()
appendLine("until [ -d \"/sdcard/Android\" ]; do sleep 1; done")
appendLine("sleep 45")
generateSusPathsSection(config.susPaths)
}
@@ -162,45 +166,48 @@ object ScriptGenerator {
appendLine("# 隐藏BL 来自 Shamiko 脚本")
appendLine(
"""
check_reset_prop() {
local NAME=$1
local EXPECTED=$2
local VALUE=$(resetprop ${'$'}NAME)
[ -z ${'$'}VALUE ] || [ ${'$'}VALUE = ${'$'}EXPECTED ] || resetprop ${'$'}NAME ${'$'}EXPECTED
}
check_missing_prop() {
local NAME=$1
local EXPECTED=$2
local VALUE=$(resetprop ${'$'}NAME)
[ -z ${'$'}VALUE ] && resetprop ${'$'}NAME ${'$'}EXPECTED
}
check_missing_match_prop() {
local NAME=$1
local EXPECTED=$2
local VALUE=$(resetprop ${'$'}NAME)
[ -z ${'$'}VALUE ] || [ ${'$'}VALUE = ${'$'}EXPECTED ] || resetprop ${'$'}NAME ${'$'}EXPECTED
[ -z ${'$'}VALUE ] && resetprop ${'$'}NAME ${'$'}EXPECTED
}
contains_reset_prop() {
local NAME=$1
local CONTAINS=$2
local NEWVAL=$3
[[ "$(resetprop ${'$'}NAME)" = *"${'$'}CONTAINS"* ]] && resetprop ${'$'}NAME ${'$'}NEWVAL
}
""".trimIndent())
RESETPROP_BIN="/data/adb/ksu/bin/resetprop"
check_reset_prop() {
local NAME=$1
local EXPECTED=$2
local VALUE=$("${'$'}RESETPROP_BIN" ${'$'}NAME)
[ -z ${'$'}VALUE ] || [ ${'$'}VALUE = ${'$'}EXPECTED ] || "${'$'}RESETPROP_BIN" ${'$'}NAME ${'$'}EXPECTED
}
check_missing_prop() {
local NAME=$1
local EXPECTED=$2
local VALUE=$("${'$'}RESETPROP_BIN" ${'$'}NAME)
[ -z ${'$'}VALUE ] && "${'$'}RESETPROP_BIN" ${'$'}NAME ${'$'}EXPECTED
}
check_missing_match_prop() {
local NAME=$1
local EXPECTED=$2
local VALUE=$("${'$'}RESETPROP_BIN" ${'$'}NAME)
[ -z ${'$'}VALUE ] || [ ${'$'}VALUE = ${'$'}EXPECTED ] || "${'$'}RESETPROP_BIN" ${'$'}NAME ${'$'}EXPECTED
[ -z ${'$'}VALUE ] && "${'$'}RESETPROP_BIN" ${'$'}NAME ${'$'}EXPECTED
}
contains_reset_prop() {
local NAME=$1
local CONTAINS=$2
local NEWVAL=$3
[[ "$("${'$'}RESETPROP_BIN" ${'$'}NAME)" = *"${'$'}CONTAINS"* ]] && "${'$'}RESETPROP_BIN" ${'$'}NAME ${'$'}NEWVAL
}
""".trimIndent())
appendLine()
appendLine("resetprop -w sys.boot_completed 0")
appendLine("sleep 30")
appendLine()
appendLine("\"${'$'}RESETPROP_BIN\" -w sys.boot_completed 0")
// 添加所有系统属性重置
val systemProps = listOf(
"ro.boot.vbmeta.invalidate_on_error" to "yes",
"ro.boot.vbmeta.avb_version" to "1.2",
"ro.boot.vbmeta.hash_alg" to "sha256",
"ro.boot.vbmeta.size" to "19968",
"ro.boot.vbmeta.device_state" to "locked",
"ro.boot.verifiedbootstate" to "green",
"ro.boot.flash.locked" to "1",
@@ -327,6 +334,7 @@ object ScriptGenerator {
/**
* 生成boot-completed.sh脚本内容
*/
@SuppressLint("SdCardPath")
private fun generateBootCompletedScript(config: SuSFSManager.ModuleConfig): String {
return buildString {
appendLine("#!/system/bin/sh")
@@ -352,6 +360,10 @@ object ScriptGenerator {
// 路径设置和SUS路径设置
if (config.susPaths.isNotEmpty()) {
generatePathSettingSection(config.androidDataPath, config.sdcardPath)
appendLine()
appendLine("until [ -d \"/sdcard/Android\" ]; do sleep 1; done")
appendLine("sleep 45")
appendLine()
generateSusPathsSection(config.susPaths)
}
}
@@ -378,8 +390,8 @@ object ScriptGenerator {
* 生成module.prop文件内容
*/
fun generateModuleProp(moduleId: String): String {
val moduleVersion = "v1.0.1"
val moduleVersionCode = "1001"
val moduleVersion = "v1.0.2"
val moduleVersionCode = "1002"
return """
id=$moduleId