manager: support meta module (#616)

This commit is contained in:
AlexLiuDev233
2025-11-22 00:24:01 +08:00
committed by GitHub
parent de6dc65a56
commit a772a0f82d
11 changed files with 241 additions and 10 deletions

View File

@@ -33,6 +33,8 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.activity.ComponentActivity
import androidx.compose.material.icons.outlined.Info
import androidx.compose.ui.platform.LocalUriHandler
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
@@ -52,8 +54,10 @@ import java.io.File
import java.text.SimpleDateFormat
import java.util.*
import androidx.core.content.edit
import com.sukisu.ultra.ui.component.rememberCustomDialog
import com.sukisu.ultra.ui.util.module.ModuleOperationUtils
import com.sukisu.ultra.ui.util.module.ModuleUtils
import com.topjohnwu.superuser.io.SuFile
/**
* @author ShirkNeko
@@ -152,6 +156,7 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
var tempText: String
val logContent = rememberSaveable { StringBuilder() }
var showFloatAction by rememberSaveable { mutableStateOf(false) }
var shouldWarningUserMetaModule by rememberSaveable { mutableStateOf(false) }
// 添加状态跟踪是否已经完成刷写
var hasFlashCompleted by rememberSaveable { mutableStateOf(false) }
var hasExecuted by rememberSaveable { mutableStateOf(false) }
@@ -170,6 +175,39 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
val logSavedString = stringResource(R.string.log_saved)
val installingModuleString = stringResource(R.string.installing_module)
val alertDialog = rememberCustomDialog { dismiss: () -> Unit ->
val uriHandler = LocalUriHandler.current
AlertDialog(
onDismissRequest = { dismiss() },
icon = {
Icon(Icons.Outlined.Info, contentDescription = null)
},
title = {
Row(modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
Text(text = stringResource(R.string.warning_of_meta_module_title))
}
},
text = {
Text(text = stringResource(R.string.warning_of_meta_module_summary))
},
confirmButton = {
FilledTonalButton(onClick = { dismiss() }) {
Text(text = stringResource(id = android.R.string.ok))
}
},
dismissButton = {
OutlinedButton(onClick = {
uriHandler.openUri("https://kernelsu.org/guide/metamodule.html")
}) {
Text(text = stringResource(id = R.string.learn_more))
}
},
)
}
// 当前模块安装状态
val currentStatus = moduleInstallStatus.value
@@ -182,16 +220,19 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
totalModules = flashIt.uris.size,
currentModule = 1
)
shouldWarningUserMetaModule = false
hasFlashCompleted = false
hasExecuted = false
moduleVerificationMap.clear()
}
}
is FlashIt.FlashModuleUpdate -> {
shouldWarningUserMetaModule = false
hasUpdateCompleted = false
hasUpdateExecuted = false
}
else -> {
shouldWarningUserMetaModule = false
hasFlashCompleted = false
hasExecuted = false
}
@@ -240,10 +281,29 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
}
hasUpdateCompleted = true
if (!hasMetaModule() && code == 0) {
// 如果没安装 MetaModule且此模块需要挂载并且当前模块安装成功警告用户
scope.launch {
val mountOldDirectory = SuFile.open("/data/adb/modules/${getModuleIdFromUri(context,flashIt.uri)}/system")
val mountNewDirectory = SuFile.open("/data/adb/modules_update/${getModuleIdFromUri(context,flashIt.uri)}/system")
if (!(mountNewDirectory.isDirectory) && !(mountOldDirectory.isDirectory)) return@launch
shouldWarningUserMetaModule = true
alertDialog.show()
shouldWarningUserMetaModule = false
}
}
// 如果是外部安装或需要自动退出的模块更新且不需要重启,延迟后自动返回
if (isExternalInstall || shouldAutoExit) {
scope.launch {
while (shouldWarningUserMetaModule) {
kotlinx.coroutines.delay(100)
}
kotlinx.coroutines.delay(1000)
while (shouldWarningUserMetaModule) {
kotlinx.coroutines.delay(100)
}
if (shouldAutoExit) {
val sharedPref = context.getSharedPreferences("kernel_flash_prefs", Context.MODE_PRIVATE)
sharedPref.edit { remove("auto_exit_after_flash") }
@@ -334,6 +394,38 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
}
hasFlashCompleted = true
if (!hasMetaModule() && code == 0) {
// 没有 MetaModule且安装成功检查此模块是否有自动挂载
scope.launch {
var mountOldDirectory : File
var mountNewDirectory : File
when (flashIt) {
is FlashIt.FlashModules -> {
mountOldDirectory = SuFile.open("/data/adb/modules/${getModuleIdFromUri(context,flashIt.uris[flashIt.currentIndex])}/system")
mountNewDirectory = SuFile.open("/data/adb/modules_update/${getModuleIdFromUri(context,flashIt.uris[flashIt.currentIndex])}/system")
}
is FlashIt.FlashModule -> {
mountOldDirectory = SuFile.open("/data/adb/modules/${getModuleIdFromUri(context,flashIt.uri)}/system")
mountNewDirectory = SuFile.open("/data/adb/modules_update/${getModuleIdFromUri(context,flashIt.uri)}/system")
}
is FlashIt.FlashModuleUpdate -> {
mountOldDirectory = SuFile.open("/data/adb/modules/${getModuleIdFromUri(context,flashIt.uri)}/system")
mountNewDirectory = SuFile.open("/data/adb/modules_update/${getModuleIdFromUri(context,flashIt.uri)}/system")
}
else -> return@launch
}
if (!mountNewDirectory.isDirectory && !mountOldDirectory.isDirectory) return@launch
shouldWarningUserMetaModule = true
if (!hasMetaModule() && (flashIt !is FlashIt.FlashModules || flashIt.currentIndex >= flashIt.uris.size - 1)) {
// 如果没有 MetaModule且当前不是多模块刷写或是最后一个需要自动刷写的模块而且有模块需要挂载警告用户
alertDialog.show()
}
}
}
if (flashIt is FlashIt.FlashModules && flashIt.currentIndex < flashIt.uris.size - 1) {
val nextFlashIt = flashIt.copy(
@@ -346,7 +438,13 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
} else if ((isExternalInstall || shouldAutoExit) && flashIt is FlashIt.FlashModules && flashIt.currentIndex >= flashIt.uris.size - 1) {
// 如果是外部安装或需要自动退出且是最后一个模块,安装完成后自动返回
scope.launch {
while (shouldWarningUserMetaModule) {
kotlinx.coroutines.delay(100)
}
kotlinx.coroutines.delay(1000)
while (shouldWarningUserMetaModule) {
kotlinx.coroutines.delay(100)
}
if (shouldAutoExit) {
val sharedPref = context.getSharedPreferences("kernel_flash_prefs", Context.MODE_PRIVATE)
sharedPref.edit { remove("auto_exit_after_flash") }
@@ -356,7 +454,13 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
} else if ((isExternalInstall || shouldAutoExit) && flashIt is FlashIt.FlashModule) {
// 如果是外部安装或需要自动退出的单个模块,安装完成后自动返回
scope.launch {
while (shouldWarningUserMetaModule) {
kotlinx.coroutines.delay(100)
}
kotlinx.coroutines.delay(1000)
while (shouldWarningUserMetaModule) {
kotlinx.coroutines.delay(100)
}
if (shouldAutoExit) {
val sharedPref = context.getSharedPreferences("kernel_flash_prefs", Context.MODE_PRIVATE)
sharedPref.edit { remove("auto_exit_after_flash") }
@@ -705,6 +809,22 @@ suspend fun getModuleNameFromUri(context: Context, uri: Uri): String {
}
}
suspend fun getModuleIdFromUri(context: Context, uri: Uri): String? {
return withContext(Dispatchers.IO) {
try {
if (uri == Uri.EMPTY) {
return@withContext null
}
if (!ModuleUtils.isUriAccessible(context, uri)) {
return@withContext null
}
ModuleUtils.extractModuleId(context, uri)
} catch (_: Exception) {
null
}
}
}
@Parcelize
sealed class FlashIt : Parcelable {
data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean, val partition: String? = null) : FlashIt()

View File

@@ -182,6 +182,7 @@ fun HomeScreen(navigator: DestinationsNavigator) {
isSimpleMode = viewModel.isSimpleMode,
isHideSusfsStatus = viewModel.isHideSusfsStatus,
isHideZygiskImplement = viewModel.isHideZygiskImplement,
isHideMetaModuleImplement = viewModel.isHideMetaModuleImplement,
showKpmInfo = viewModel.showKpmInfo,
lkmMode = viewModel.systemStatus.lkmMode,
)
@@ -652,6 +653,7 @@ private fun InfoCard(
isSimpleMode: Boolean,
isHideSusfsStatus: Boolean,
isHideZygiskImplement: Boolean,
isHideMetaModuleImplement: Boolean,
showKpmInfo: Boolean,
lkmMode: Boolean?
) {
@@ -784,6 +786,14 @@ private fun InfoCard(
)
}
if (!isHideMetaModuleImplement && !isSimpleMode && systemInfo.metaModuleImplement != "None") {
InfoCardItem(
stringResource(R.string.home_meta_module_implement),
systemInfo.metaModuleImplement,
icon = Icons.Default.Extension,
)
}
if (!isSimpleMode) {
if (lkmMode != true && !showKpmInfo) {
val displayVersion =

View File

@@ -752,6 +752,7 @@ private fun ModuleList(
val uninstall = stringResource(R.string.uninstall)
val cancel = stringResource(android.R.string.cancel)
val moduleUninstallConfirm = stringResource(R.string.module_uninstall_confirm)
val metaModuleUninstallConfirm = stringResource(R.string.metamodule_uninstall_confirm)
val updateText = stringResource(R.string.module_update)
val changelogText = stringResource(R.string.module_changelog)
val downloadingText = stringResource(R.string.module_downloading)
@@ -847,9 +848,10 @@ private fun ModuleList(
suspend fun onModuleUninstallClicked(module: ModuleViewModel.ModuleInfo) {
val isUninstall = !module.remove
if (isUninstall) {
val formatter = if (module.metamodule) metaModuleUninstallConfirm else moduleUninstallConfirm
val confirmResult = confirmDialog.awaitConfirm(
moduleStr,
content = moduleUninstallConfirm.format(module.name),
content = formatter.format(module.name),
confirm = uninstall,
dismiss = cancel
)
@@ -1199,6 +1201,22 @@ fun ModuleItem(
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.fillMaxWidth()
) {
if (module.metamodule) {
Surface(
shape = RoundedCornerShape(4.dp),
color = MaterialTheme.colorScheme.primary,
modifier = Modifier
) {
Text(
text = "META",
style = MaterialTheme.typography.labelSmall,
modifier = Modifier.padding(horizontal = 4.dp, vertical = 1.dp),
color = MaterialTheme.colorScheme.onPrimary,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
Surface(
shape = RoundedCornerShape(4.dp),
color = MaterialTheme.colorScheme.primary,
@@ -1329,8 +1347,9 @@ fun ModuleItemPreview() {
update = true,
remove = false,
updateJson = "",
hasWebUi = false,
hasActionScript = false,
hasWebUi = true,
hasActionScript = true,
metamodule = true,
dirId = "dirId",
config = ModuleConfig(),
isVerified = true,

View File

@@ -19,8 +19,10 @@ import kotlinx.parcelize.Parcelize
import com.sukisu.ultra.BuildConfig
import com.sukisu.ultra.Natives
import com.sukisu.ultra.ksuApp
import com.topjohnwu.superuser.io.SuFile
import org.json.JSONArray
import java.io.File
import java.util.Properties
/**
@@ -111,6 +113,10 @@ fun install() {
Log.w(TAG, "install result: $result, cost: ${SystemClock.elapsedRealtime() - start}ms")
}
fun hasMetaModule(): Boolean {
return getMetaModuleImplement() != "None"
}
fun listModules(): String {
val shell = getRootShell()
@@ -575,6 +581,26 @@ fun getSuSFSFeatures(): String {
return runCmd(shell, cmd)
}
fun getMetaModuleImplement(): String {
try {
val metaModuleProp = SuFile.open("/data/adb/metamodule/module.prop")
if (!metaModuleProp.isFile) {
Log.i(TAG, "Meta module implement: None")
return "None"
}
val prop = Properties()
prop.load(metaModuleProp.newInputStream())
val name = prop.getProperty("name")
Log.i(TAG, "Meta module implement: $name")
return name
} catch (t : Throwable) {
Log.i(TAG, "Meta module implement: None")
return "None"
}
}
fun getZygiskImplement(): String {
val zygiskModuleIds = listOf(
"zygisksu",

View File

@@ -54,7 +54,8 @@ class HomeViewModel : ViewModel() {
val kpmModuleCount: Int = 0,
val managersList: Natives.ManagersList? = null,
val isDynamicSignEnabled: Boolean = false,
val zygiskImplement: String = ""
val zygiskImplement: String = "",
val metaModuleImplement: String = ""
)
// 状态变量
@@ -79,6 +80,8 @@ class HomeViewModel : ViewModel() {
private set
var isHideZygiskImplement by mutableStateOf(false)
private set
var isHideMetaModuleImplement by mutableStateOf(false)
private set
var isHideLinkCard by mutableStateOf(false)
private set
var showKpmInfo by mutableStateOf(false)
@@ -109,6 +112,7 @@ class HomeViewModel : ViewModel() {
isHideSusfsStatus = settingsPrefs.getBoolean("is_hide_susfs_status", false)
isHideLinkCard = settingsPrefs.getBoolean("is_hide_link_card", false)
isHideZygiskImplement = settingsPrefs.getBoolean("is_hide_zygisk_Implement", false)
isHideMetaModuleImplement = settingsPrefs.getBoolean("is_hide_meta_module_Implement", false)
showKpmInfo = settingsPrefs.getBoolean("show_kpm_info", false)
}
}
@@ -222,7 +226,8 @@ class HomeViewModel : ViewModel() {
superuserCount = moduleInfo.second,
moduleCount = moduleInfo.third,
kpmModuleCount = moduleInfo.fourth,
zygiskImplement = moduleInfo.fifth
zygiskImplement = moduleInfo.fifth,
metaModuleImplement = moduleInfo.sixth
)
}
@@ -398,7 +403,7 @@ class HomeViewModel : ViewModel() {
}
}
private suspend fun loadModuleInfo(): Tuple5<String, Int, Int, Int, String> {
private suspend fun loadModuleInfo(): Tuple6<String, Int, Int, Int, String, String> {
return withContext(Dispatchers.IO) {
val kpmVersion = try {
getKpmVersion()
@@ -430,7 +435,13 @@ class HomeViewModel : ViewModel() {
"None"
}
Tuple5(kpmVersion, superuserCount, moduleCount, kpmModuleCount, zygiskImplement)
val metaModuleImplement = try {
getMetaModuleImplement()
} catch (_: Exception) {
"None"
}
Tuple6(kpmVersion, superuserCount, moduleCount, kpmModuleCount, zygiskImplement, metaModuleImplement)
}
}
@@ -567,6 +578,15 @@ class HomeViewModel : ViewModel() {
}
}
data class Tuple6<T1, T2, T3, T4, T5, T6>(
val first: T1,
val second: T2,
val third: T3,
val fourth: T4,
val fifth: T5,
val sixth: T6
)
data class Tuple5<T1, T2, T3, T4, T5>(
val first: T1,
val second: T2,

View File

@@ -85,6 +85,7 @@ class ModuleViewModel : ViewModel() {
val updateJson: String,
val hasWebUi: Boolean,
val hasActionScript: Boolean,
val metamodule: Boolean,
val dirId: String, // real module id (dir name)
var config: ModuleConfig? = null,
var isVerified: Boolean = false, // 添加验证状态字段
@@ -151,6 +152,7 @@ class ModuleViewModel : ViewModel() {
obj.optString("updateJson"),
obj.getBooleanCompat("web"),
obj.getBooleanCompat("action"),
obj.getBooleanCompat("metamodule"),
obj.optString("dir_id", obj.getString("id"))
)
}.toList()
@@ -305,6 +307,7 @@ fun ModuleViewModel.ModuleInfo.copy(
updateJson: String = this.updateJson,
hasWebUi: Boolean = this.hasWebUi,
hasActionScript: Boolean = this.hasActionScript,
metamodule: Boolean = this.metamodule,
dirId: String = this.dirId,
config: ModuleConfig? = this.config,
isVerified: Boolean = this.isVerified,
@@ -312,7 +315,7 @@ fun ModuleViewModel.ModuleInfo.copy(
): ModuleViewModel.ModuleInfo {
return ModuleViewModel.ModuleInfo(
id, name, author, version, versionCode, description,
enabled, update, remove, updateJson, hasWebUi, hasActionScript,
enabled, update, remove, updateJson, hasWebUi, hasActionScript, metamodule,
dirId, config, isVerified, verificationTimestamp
)
}

View File

@@ -312,6 +312,16 @@ private fun HideOptionsSettings(
onChange = handlers::handleHideZygiskImplementChange
)
// 元模块实现状态信息
SwitchSettingItem(
icon = Icons.Filled.VisibilityOff,
title = stringResource(R.string.hide_meta_module_implement),
summary = stringResource(R.string.hide_meta_module_implement_summary),
checked = state.isHideMetaModuleImplement,
onChange = handlers::handleHideMetaModuleImplementChange
)
// KPM 状态信息隐藏
if (Natives.version >= Natives.MINIMAL_SUPPORTED_KPM) {
SwitchSettingItem(
icon = Icons.Filled.VisibilityOff,

View File

@@ -341,6 +341,14 @@ class MoreSettingsHandlers(
state.isHideZygiskImplement = newValue
}
/**
* 处理隐藏元模块实现变更
*/
fun handleHideMetaModuleImplementChange(newValue: Boolean) {
prefs.edit { putBoolean("is_hide_meta_module_Implement", newValue) }
state.isHideMetaModuleImplement = newValue
}
/**
* 处理隐藏链接卡片变更
*/

View File

@@ -60,6 +60,7 @@ class MoreSettingsState(
var isHideOtherInfo by mutableStateOf(prefs.getBoolean("is_hide_other_info", false))
var isShowKpmInfo by mutableStateOf(prefs.getBoolean("show_kpm_info", false))
var isHideZygiskImplement by mutableStateOf(prefs.getBoolean("is_hide_zygisk_Implement", false))
var isHideMetaModuleImplement by mutableStateOf(prefs.getBoolean("is_hide_meta_module_Implement", false))
var isHideSusfsStatus by mutableStateOf(prefs.getBoolean("is_hide_susfs_status", false))
var isHideLinkCard by mutableStateOf(prefs.getBoolean("is_hide_link_card", false))
var isHideTagRow by mutableStateOf(prefs.getBoolean("is_hide_tag_row", false))

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="learn_more">了解更多</string>
<string name="home">主页</string>
<string name="home_not_installed">未安装</string>
<string name="home_click_to_install">点击安装</string>
@@ -34,6 +35,7 @@
<string name="reboot_edl">重启到 EDL</string>
<string name="about">关于</string>
<string name="module_uninstall_confirm">确定要卸载模块 %s 吗?</string>
<string name="metamodule_uninstall_confirm">"您确定要卸载模块 %s 吗?此操作将影响所有模块,并且元模块提供的某些功能(如挂载)将不再工作。 "</string>
<string name="module_uninstall_success">%s 已卸载</string>
<string name="module_uninstall_failed">卸载失败:%s</string>
<string name="module_version">版本</string>
@@ -188,10 +190,12 @@
<string name="hide_susfs_status_summary">隐藏主页上的 SuSFS 状态信息</string>
<string name="hide_zygisk_implement">隐藏 Zygisk 状态信息</string>
<string name="hide_zygisk_implement_summary">隐藏主页上的 Zygisk 实现状态信息</string>
<string name="hide_meta_module_implement">隐藏元模块状态信息</string>
<string name="hide_meta_module_implement_summary">隐藏主页上的元模块实现状态信息</string>
<string name="hide_link_card">隐藏链接卡片</string>
<string name="hide_link_card_summary">隐藏主页上的链接卡片信息</string>
<string name="hide_tag_card">隐藏模块标签行</string>
<string name="hide_tag_card_summary">隐藏模块卡片中的文件夹名称大小标签</string>
<string name="hide_tag_card_summary">隐藏模块卡片中的文件夹名称大小标签和是否为元模块</string>
<string name="theme_mode">主题模式</string>
<string name="theme_follow_system">跟随系统</string>
<string name="theme_light">浅色</string>
@@ -328,6 +332,8 @@
<string name="module_failed_count">%d 个模块安装失败</string>
<string name="module_download_error">模块下载失败</string>
<string name="kernel_flashing">内核刷写</string>
<string name="warning_of_meta_module_title">需要元模块</string>
<string name="warning_of_meta_module_summary">这个模块需要挂载一些文件。需要安装元模块,使它正常工作</string>
<!-- 分类相关 -->
<string name="category_all_apps">全部</string>
<string name="category_root_apps">Root</string>
@@ -588,6 +594,7 @@
<string name="no_active_manager">无活跃管理器</string>
<string name="default_signature">SukiSU</string>
<string name="home_zygisk_implement">Zygisk 实现</string>
<string name="home_meta_module_implement">元模块实现</string>
<!-- 循环路径相关 -->
<string name="susfs_tab_sus_loop_paths">SuS 循环路径</string>
<string name="susfs_add_sus_loop_path">添加 SuS 循环路径</string>

View File

@@ -2,6 +2,7 @@
<resources>
<string name="app_name" translatable="false">SukiSU Ultra</string>
<string name="home">Home</string>
<string name="learn_more">Learn more</string>
<string name="home_not_installed">Not installed</string>
<string name="home_click_to_install">Click to install</string>
<string name="home_working">Working</string>
@@ -35,6 +36,7 @@
<string name="reboot_edl">Reboot to EDL</string>
<string name="about">About</string>
<string name="module_uninstall_confirm">Are you sure you want to uninstall module %s?</string>
<string name="metamodule_uninstall_confirm">"Are you sure you want to uninstall module %s? This action will affect all modules, and certain features provided by the metamodule (such as mounting) will no longer work. "</string>
<string name="module_uninstall_success">%s uninstalled</string>
<string name="module_uninstall_failed">Failed to uninstall: %s</string>
<string name="module_version">Version</string>
@@ -190,10 +192,12 @@
<string name="hide_susfs_status_summary">Hide SuSFS status information on the home page</string>
<string name="hide_zygisk_implement">Hide Zygisk status</string>
<string name="hide_zygisk_implement_summary">Hide Zygisk implementation information on the home page</string>
<string name="hide_meta_module_implement">Hide Meta Module status</string>
<string name="hide_meta_module_implement_summary">Hide Meta Module implementation information on the home page</string>
<string name="hide_link_card">Hide Link Card Status</string>
<string name="hide_link_card_summary">Hide link card information on the home page</string>
<string name="hide_tag_card">Hide module label rows</string>
<string name="hide_tag_card_summary">Hide folder name and size labels in module cards</string>
<string name="hide_tag_card_summary">Hide folder name,size,metamodule notice labels in module cards</string>
<string name="theme_mode">Theme</string>
<string name="theme_follow_system">Follow system</string>
<string name="theme_light">Light</string>
@@ -331,6 +335,8 @@
<string name="module_failed_count">%d Failed to install a new module</string>
<string name="module_download_error">Module download failed</string>
<string name="kernel_flashing">Kernel Flashing</string>
<string name="warning_of_meta_module_title">Require Meta module</string>
<string name="warning_of_meta_module_summary">This module want to mount /system, meta module will handle that. Otherwise, it might not work.</string>
<!-- 分类相关 -->
<string name="category_all_apps">All</string>
<string name="category_root_apps">Root</string>
@@ -591,6 +597,7 @@
<string name="no_active_manager">No active manager</string>
<string name="default_signature">SukiSU</string>
<string name="home_zygisk_implement">Zygisk implement</string>
<string name="home_meta_module_implement">Meta Module implement</string>
<!-- 循环路径相关 -->
<string name="susfs_tab_sus_loop_paths">SUS Loop Paths</string>
<string name="susfs_add_sus_loop_path">Add SUS Loop Path</string>