-Optimize file permission requests, simplify backup and restore logic, and directly process user-selected files

- This will fix an issue where some devices were unable to back up their app lists
This commit is contained in:
ShirkNeko
2025-03-21 03:03:27 +08:00
parent aefd2b57cf
commit ba928150d5
2 changed files with 31 additions and 54 deletions

View File

@@ -3,10 +3,9 @@
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
tools:ignore="ScopedStorage" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<application <application
android:name=".KernelSUApplication" android:name=".KernelSUApplication"
@@ -19,6 +18,7 @@
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.KernelSU" android:theme="@style/Theme.KernelSU"
android:requestLegacyExternalStorage="true"
tools:targetApi="34"> tools:targetApi="34">
<activity <activity
android:name=".ui.MainActivity" android:name=".ui.MainActivity"

View File

@@ -18,7 +18,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import shirkneko.zako.sukisu.R import shirkneko.zako.sukisu.R
import java.io.BufferedReader import java.io.BufferedReader
import java.io.File
import java.io.IOException import java.io.IOException
import java.io.InputStreamReader import java.io.InputStreamReader
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@@ -45,30 +44,25 @@ object ModuleModify {
try { try {
val busyboxPath = "/data/adb/ksu/bin/busybox" val busyboxPath = "/data/adb/ksu/bin/busybox"
val moduleDir = "/data/adb/modules" val moduleDir = "/data/adb/modules"
val tempFile = File(context.cacheDir, "backup_${System.currentTimeMillis()}.tar.gz")
val tempPath = tempFile.absolutePath
// 直接将tar输出重定向到用户选择的文件
val command = """ val command = """
cd "$moduleDir" && cd "$moduleDir" &&
$busyboxPath tar -czvf "$tempPath" ./* $busyboxPath tar -cz ./* > /proc/self/fd/1
""".trimIndent() """.trimIndent()
val process = Runtime.getRuntime().exec(arrayOf("su", "-c", command)) val process = Runtime.getRuntime().exec(arrayOf("su", "-c", command))
process.waitFor()
// 直接将tar输出写入到用户选择的文件
context.contentResolver.openOutputStream(uri)?.use { output ->
process.inputStream.copyTo(output)
}
val error = BufferedReader(InputStreamReader(process.errorStream)).readText() val error = BufferedReader(InputStreamReader(process.errorStream)).readText()
if (process.exitValue() != 0) { if (process.exitValue() != 0) {
throw IOException(context.getString(R.string.command_execution_failed, error)) throw IOException(context.getString(R.string.command_execution_failed, error))
} }
context.contentResolver.openOutputStream(uri)?.use { output ->
tempFile.inputStream().use { input ->
input.copyTo(output)
}
}
tempFile.delete()
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
snackBarHost.showSnackbar( snackBarHost.showSnackbar(
context.getString(R.string.backup_success), context.getString(R.string.backup_success),
@@ -96,23 +90,15 @@ object ModuleModify {
try { try {
val busyboxPath = "/data/adb/ksu/bin/busybox" val busyboxPath = "/data/adb/ksu/bin/busybox"
val moduleDir = "/data/adb/modules" val moduleDir = "/data/adb/modules"
val tempFile = File(context.cacheDir, "temp_restore.tar.gz").apply {
if (exists()) delete() // 直接从用户选择的文件读取并解压
} val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "$busyboxPath tar -xz -C $moduleDir"))
context.contentResolver.openInputStream(uri)?.use { input -> context.contentResolver.openInputStream(uri)?.use { input ->
tempFile.outputStream().use { output -> input.copyTo(process.outputStream)
input.copyTo(output)
}
} }
process.outputStream.close()
val command = """
cd "$moduleDir" &&
rm -rf * &&
$busyboxPath tar -xzvf "${tempFile.absolutePath}"
""".trimIndent()
val process = Runtime.getRuntime().exec(arrayOf("su", "-c", command))
process.waitFor() process.waitFor()
val error = BufferedReader(InputStreamReader(process.errorStream)).readText() val error = BufferedReader(InputStreamReader(process.errorStream)).readText()
@@ -120,8 +106,6 @@ object ModuleModify {
throw IOException(context.getString(R.string.command_execution_failed, error)) throw IOException(context.getString(R.string.command_execution_failed, error))
} }
tempFile.delete()
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
val snackbarResult = snackBarHost.showSnackbar( val snackbarResult = snackBarHost.showSnackbar(
message = context.getString(R.string.restore_success), message = context.getString(R.string.restore_success),
@@ -166,25 +150,19 @@ object ModuleModify {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
try { try {
val allowlistPath = "/data/adb/ksu/.allowlist" val allowlistPath = "/data/adb/ksu/.allowlist"
val tempFile = File(context.cacheDir, "allowlist_backup_${System.currentTimeMillis()}")
val command = "cp $allowlistPath ${tempFile.absolutePath}" // 直接复制文件到用户选择的位置
val process = Runtime.getRuntime().exec(arrayOf("su", "-c", command)) val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "cat $allowlistPath"))
process.waitFor()
context.contentResolver.openOutputStream(uri)?.use { output ->
process.inputStream.copyTo(output)
}
val error = BufferedReader(InputStreamReader(process.errorStream)).readText() val error = BufferedReader(InputStreamReader(process.errorStream)).readText()
if (process.exitValue() != 0) { if (process.exitValue() != 0) {
throw IOException(context.getString(R.string.command_execution_failed, error)) throw IOException(context.getString(R.string.command_execution_failed, error))
} }
context.contentResolver.openOutputStream(uri)?.use { output ->
tempFile.inputStream().use { input ->
input.copyTo(output)
}
}
tempFile.delete()
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
snackBarHost.showSnackbar( snackBarHost.showSnackbar(
context.getString(R.string.allowlist_backup_success), context.getString(R.string.allowlist_backup_success),
@@ -211,18 +189,15 @@ object ModuleModify {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
try { try {
val allowlistPath = "/data/adb/ksu/.allowlist" val allowlistPath = "/data/adb/ksu/.allowlist"
val tempFile = File(context.cacheDir, "allowlist_restore_temp").apply {
if (exists()) delete() // 直接从用户选择的文件读取并写入到目标位置
} val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "cat > $allowlistPath"))
context.contentResolver.openInputStream(uri)?.use { input -> context.contentResolver.openInputStream(uri)?.use { input ->
tempFile.outputStream().use { output -> input.copyTo(process.outputStream)
input.copyTo(output)
}
} }
process.outputStream.close()
val command = "cp ${tempFile.absolutePath} $allowlistPath"
val process = Runtime.getRuntime().exec(arrayOf("su", "-c", command))
process.waitFor() process.waitFor()
val error = BufferedReader(InputStreamReader(process.errorStream)).readText() val error = BufferedReader(InputStreamReader(process.errorStream)).readText()
@@ -230,8 +205,6 @@ object ModuleModify {
throw IOException(context.getString(R.string.command_execution_failed, error)) throw IOException(context.getString(R.string.command_execution_failed, error))
} }
tempFile.delete()
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
snackBarHost.showSnackbar( snackBarHost.showSnackbar(
context.getString(R.string.allowlist_restore_success), context.getString(R.string.allowlist_restore_success),
@@ -350,4 +323,8 @@ object ModuleModify {
type = "application/octet-stream" type = "application/octet-stream"
} }
} }
private fun reboot() {
Runtime.getRuntime().exec(arrayOf("su", "-c", "reboot"))
}
} }