Add vpn hide xposed module

This commit is contained in:
世界
2026-01-10 21:31:04 +08:00
parent 8a8686b3df
commit cd83cbfe9e
152 changed files with 12994 additions and 2782 deletions
@@ -27,6 +27,14 @@ class GitHubUpdateChecker : Closeable {
private val json = Json { ignoreUnknownKeys = true }
fun checkUpdate(track: UpdateTrack): UpdateInfo? {
return getLatestUpdate(track, checkVersion = true)
}
fun forceGetLatestUpdate(track: UpdateTrack): UpdateInfo? {
return getLatestUpdate(track, checkVersion = false)
}
private fun getLatestUpdate(track: UpdateTrack, checkVersion: Boolean): UpdateInfo? {
val includePrerelease = track == UpdateTrack.BETA
val release = getLatestRelease(includePrerelease) ?: return null
@@ -36,7 +44,7 @@ class GitHubUpdateChecker : Closeable {
val metadata = downloadMetadata(release)!!
if (metadata.versionCode <= BuildConfig.VERSION_CODE) {
if (checkVersion && metadata.versionCode <= BuildConfig.VERSION_CODE) {
return null
}
@@ -1,12 +1,18 @@
package io.nekohasekai.sfa.vendor
import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import android.os.ParcelFileDescriptor
import com.topjohnwu.superuser.ipc.RootService
import io.nekohasekai.sfa.Application
import io.nekohasekai.sfa.bg.IRootService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import java.io.BufferedReader
import java.io.BufferedWriter
import java.io.File
import java.io.InputStreamReader
import java.io.OutputStreamWriter
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
object RootInstaller {
@@ -20,20 +26,63 @@ object RootInstaller {
}
}
suspend fun install(apkFile: File): Result<Unit> = withContext(Dispatchers.IO) {
try {
val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "pm install -r \"${apkFile.absolutePath}\""))
val reader = BufferedReader(InputStreamReader(process.inputStream))
val output = reader.readText()
val exitCode = process.waitFor()
if (exitCode == 0 && output.contains("Success")) {
Result.success(Unit)
} else {
Result.failure(Exception("Installation failed: $output"))
suspend fun install(apkFile: File) {
withContext(Dispatchers.IO) {
bindRootService().use { handle ->
ParcelFileDescriptor.open(apkFile, ParcelFileDescriptor.MODE_READ_ONLY).use { pfd ->
handle.service.installPackage(
pfd,
apkFile.length(),
android.os.Process.myUserHandle().hashCode()
)
}
}
} catch (e: Exception) {
Result.failure(e)
}
}
private suspend fun bindRootService(): RootServiceHandle {
return withContext(Dispatchers.Main) {
suspendCancellableCoroutine { continuation ->
val conn = object : ServiceConnection {
override fun onServiceConnected(name: android.content.ComponentName?, binder: IBinder?) {
val svc = if (binder != null && binder.pingBinder()) {
IRootService.Stub.asInterface(binder)
} else {
null
}
if (svc == null) {
continuation.resumeWithException(IllegalStateException("Invalid root service binder"))
return
}
continuation.resume(RootServiceHandle(this, svc))
}
override fun onServiceDisconnected(name: android.content.ComponentName?) {
// Ignored
}
}
try {
val intent = Intent(Application.application, Class.forName("io.nekohasekai.sfa.bg.RootServer"))
RootService.bind(intent, conn)
} catch (e: Throwable) {
continuation.resumeWithException(e)
return@suspendCancellableCoroutine
}
continuation.invokeOnCancellation {
RootService.unbind(conn)
}
}
}
}
private data class RootServiceHandle(
val connection: ServiceConnection,
val service: IRootService
) : java.io.Closeable {
override fun close() {
RootService.unbind(connection)
}
}
}
@@ -14,39 +14,34 @@ object SystemPackageInstaller {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
}
fun install(context: Context, apkFile: File): Result<Unit> {
return try {
val packageInstaller = context.packageManager.packageInstaller
val params = AndroidPackageInstaller.SessionParams(AndroidPackageInstaller.SessionParams.MODE_FULL_INSTALL)
params.setAppPackageName(context.packageName)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
params.setRequireUserAction(AndroidPackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED)
fun install(context: Context, apkFile: File) {
val packageInstaller = context.packageManager.packageInstaller
val params = AndroidPackageInstaller.SessionParams(AndroidPackageInstaller.SessionParams.MODE_FULL_INSTALL)
params.setAppPackageName(context.packageName)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
params.setRequireUserAction(AndroidPackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED)
}
val sessionId = packageInstaller.createSession(params)
packageInstaller.openSession(sessionId).use { session ->
session.openWrite("update.apk", 0, apkFile.length()).use { outputStream ->
FileInputStream(apkFile).use { inputStream ->
inputStream.copyTo(outputStream)
}
session.fsync(outputStream)
}
val sessionId = packageInstaller.createSession(params)
packageInstaller.openSession(sessionId).use { session ->
session.openWrite("update.apk", 0, apkFile.length()).use { outputStream ->
FileInputStream(apkFile).use { inputStream ->
inputStream.copyTo(outputStream)
}
session.fsync(outputStream)
}
val intent = Intent(context, InstallResultReceiver::class.java).apply {
action = InstallResultReceiver.ACTION_INSTALL_COMPLETE
}
val pendingIntent = PendingIntent.getBroadcast(
context,
sessionId,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)
session.commit(pendingIntent.intentSender)
val intent = Intent(context, InstallResultReceiver::class.java).apply {
action = InstallResultReceiver.ACTION_INSTALL_COMPLETE
}
Result.success(Unit)
} catch (e: Exception) {
Result.failure(e)
val pendingIntent = PendingIntent.getBroadcast(
context,
sessionId,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)
session.commit(pendingIntent.intentSender)
}
}
}
@@ -77,13 +77,8 @@ class UpdateWorker(
val apkFile = ApkDownloader().use { it.download(updateInfo.downloadUrl) }
Log.d(TAG, "Installing update...")
val result = ApkInstaller.install(appContext, apkFile)
if (result.isSuccess) {
Log.d(TAG, "Update installed successfully")
} else {
Log.e(TAG, "Update installation failed", result.exceptionOrNull())
}
ApkInstaller.install(appContext, apkFile)
Log.d(TAG, "Update installed successfully")
} else {
Log.d(TAG, "Silent install not available, update will be shown on next app launch")
}