Add vpn hide xposed module
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+66
-17
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user