Add vpn hide xposed module
This commit is contained in:
@@ -1,34 +1,27 @@
|
||||
package io.nekohasekai.sfa.vendor
|
||||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import io.nekohasekai.sfa.Application
|
||||
import io.nekohasekai.sfa.BuildConfig
|
||||
import io.nekohasekai.sfa.bg.RootClient
|
||||
import io.nekohasekai.sfa.database.Settings
|
||||
import io.nekohasekai.sfa.utils.HookStatusClient
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
object PackageQueryManager {
|
||||
|
||||
private const val TAG = "PackageQueryManager"
|
||||
|
||||
val needsPrivilegedQuery: Boolean by lazy {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
// Check if QUERY_ALL_PACKAGES is declared in manifest
|
||||
val packageInfo = Application.packageManager.getPackageInfo(
|
||||
Application.application.packageName,
|
||||
PackageManager.GET_PERMISSIONS
|
||||
)
|
||||
val hasPermission = packageInfo.requestedPermissions?.contains(
|
||||
Manifest.permission.QUERY_ALL_PACKAGES
|
||||
) == true
|
||||
!hasPermission
|
||||
} else {
|
||||
false
|
||||
val strategy: PackageQueryStrategy
|
||||
get() = when {
|
||||
HookStatusClient.status.value?.active == true -> PackageQueryStrategy.ForcedRoot
|
||||
BuildConfig.FLAVOR == "play" -> PackageQueryStrategy.UserSelected(queryMode.value)
|
||||
else -> PackageQueryStrategy.Direct
|
||||
}
|
||||
}
|
||||
|
||||
val showModeSelector: Boolean
|
||||
get() = strategy is PackageQueryStrategy.UserSelected
|
||||
|
||||
private val _queryMode = MutableStateFlow(Settings.perAppProxyPackageQueryMode)
|
||||
val queryMode: StateFlow<String> = _queryMode
|
||||
@@ -36,8 +29,8 @@ object PackageQueryManager {
|
||||
val shizukuInstalled: StateFlow<Boolean> get() = ShizukuPackageManager.shizukuInstalled
|
||||
val shizukuBinderReady: StateFlow<Boolean> get() = ShizukuPackageManager.binderReady
|
||||
val shizukuPermissionGranted: StateFlow<Boolean> get() = ShizukuPackageManager.permissionGranted
|
||||
val rootAvailable: StateFlow<Boolean?> get() = RootPackageManager.rootAvailable
|
||||
val rootServiceConnected: StateFlow<Boolean> get() = RootPackageManager.serviceConnected
|
||||
val rootAvailable: StateFlow<Boolean?> get() = RootClient.rootAvailable
|
||||
val rootServiceConnected: StateFlow<Boolean> get() = RootClient.serviceConnected
|
||||
|
||||
fun isShizukuAvailable(): Boolean =
|
||||
ShizukuPackageManager.isAvailable() && ShizukuPackageManager.checkPermission()
|
||||
@@ -55,8 +48,12 @@ object PackageQueryManager {
|
||||
ShizukuPackageManager.requestPermission()
|
||||
}
|
||||
|
||||
fun refreshShizukuState() {
|
||||
ShizukuPackageManager.refresh()
|
||||
}
|
||||
|
||||
suspend fun checkRootAvailable(): Boolean {
|
||||
return RootPackageManager.checkRootAvailable()
|
||||
return RootClient.checkRootAvailable()
|
||||
}
|
||||
|
||||
fun setQueryMode(mode: String) {
|
||||
@@ -64,26 +61,14 @@ object PackageQueryManager {
|
||||
}
|
||||
|
||||
suspend fun getInstalledPackages(flags: Int): List<PackageInfo> {
|
||||
if (!needsPrivilegedQuery) {
|
||||
return getPackagesViaPackageManager(flags)
|
||||
}
|
||||
|
||||
val mode = _queryMode.value
|
||||
|
||||
if (mode == Settings.PACKAGE_QUERY_MODE_ROOT) {
|
||||
if (rootAvailable.value != true) {
|
||||
val isAvailable = RootPackageManager.checkRootAvailable()
|
||||
if (!isAvailable) {
|
||||
throw PrivilegedAccessRequiredException("ROOT access required")
|
||||
}
|
||||
return when (val s = strategy) {
|
||||
is PackageQueryStrategy.ForcedRoot -> RootClient.getInstalledPackages(flags)
|
||||
is PackageQueryStrategy.UserSelected -> when (s.mode) {
|
||||
Settings.PACKAGE_QUERY_MODE_ROOT -> RootClient.getInstalledPackages(flags)
|
||||
else -> ShizukuPackageManager.getInstalledPackages(flags)
|
||||
}
|
||||
return RootPackageManager.getInstalledPackages(flags)
|
||||
is PackageQueryStrategy.Direct -> getPackagesViaPackageManager(flags)
|
||||
}
|
||||
|
||||
if (!isShizukuAvailable()) {
|
||||
throw PrivilegedAccessRequiredException("Shizuku access required")
|
||||
}
|
||||
return ShizukuPackageManager.getInstalledPackages(flags)
|
||||
}
|
||||
|
||||
private fun getPackagesViaPackageManager(flags: Int): List<PackageInfo> {
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
package io.nekohasekai.sfa.vendor
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.PackageInfo
|
||||
import android.os.IBinder
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.ipc.RootService
|
||||
import io.nekohasekai.sfa.Application
|
||||
import io.nekohasekai.sfa.BuildConfig
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
object RootPackageManager {
|
||||
|
||||
init {
|
||||
Shell.enableVerboseLogging = BuildConfig.DEBUG
|
||||
Shell.setDefaultBuilder(
|
||||
Shell.Builder.create()
|
||||
.setFlags(Shell.FLAG_MOUNT_MASTER)
|
||||
.setTimeout(10)
|
||||
)
|
||||
}
|
||||
|
||||
private val _rootAvailable = MutableStateFlow<Boolean?>(null)
|
||||
val rootAvailable: StateFlow<Boolean?> = _rootAvailable
|
||||
|
||||
private val _serviceConnected = MutableStateFlow(false)
|
||||
val serviceConnected: StateFlow<Boolean> = _serviceConnected
|
||||
|
||||
private var service: IRootPackageManager? = null
|
||||
private var connection: ServiceConnection? = null
|
||||
private val connectionMutex = Mutex()
|
||||
|
||||
suspend fun checkRootAvailable(): Boolean {
|
||||
Shell.getCachedShell()?.close()
|
||||
return suspendCancellableCoroutine { continuation ->
|
||||
Shell.getShell { shell ->
|
||||
val available = shell.isRoot
|
||||
_rootAvailable.value = available
|
||||
continuation.resume(available)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun bindService(): IRootPackageManager = connectionMutex.withLock {
|
||||
service?.let { return it }
|
||||
|
||||
return withContext(Dispatchers.Main) {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
val conn = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
|
||||
val svc = IRootPackageManager.Stub.asInterface(binder)
|
||||
service = svc
|
||||
connection = this
|
||||
_serviceConnected.value = true
|
||||
continuation.resume(svc)
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
service = null
|
||||
connection = null
|
||||
_serviceConnected.value = false
|
||||
}
|
||||
}
|
||||
|
||||
val intent = Intent(Application.application, RootPackageManagerService::class.java)
|
||||
RootService.bind(intent, conn)
|
||||
|
||||
continuation.invokeOnCancellation {
|
||||
RootService.unbind(conn)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun unbindService() {
|
||||
connection?.let {
|
||||
RootService.unbind(it)
|
||||
connection = null
|
||||
service = null
|
||||
_serviceConnected.value = false
|
||||
}
|
||||
}
|
||||
|
||||
private const val CHUNK_SIZE = 50
|
||||
|
||||
suspend fun getInstalledPackages(flags: Int): List<PackageInfo> {
|
||||
val svc = bindService()
|
||||
val result = mutableListOf<PackageInfo>()
|
||||
var offset = 0
|
||||
while (true) {
|
||||
val chunk = svc.getInstalledPackages(flags, offset, CHUNK_SIZE)
|
||||
if (chunk.isEmpty()) break
|
||||
result.addAll(chunk)
|
||||
offset += chunk.size
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package io.nekohasekai.sfa.vendor
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import com.topjohnwu.superuser.ipc.RootService
|
||||
|
||||
class RootPackageManagerService : RootService() {
|
||||
|
||||
private val binder = object : IRootPackageManager.Stub() {
|
||||
override fun getInstalledPackages(flags: Int, offset: Int, limit: Int): List<PackageInfo> {
|
||||
val allPackages = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
packageManager.getInstalledPackages(
|
||||
PackageManager.PackageInfoFlags.of(flags.toLong())
|
||||
)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
packageManager.getInstalledPackages(flags)
|
||||
}
|
||||
val endIndex = minOf(offset + limit, allPackages.size)
|
||||
if (offset >= allPackages.size) {
|
||||
return emptyList()
|
||||
}
|
||||
return allPackages.subList(offset, endIndex)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder {
|
||||
return binder
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,12 @@
|
||||
package io.nekohasekai.sfa.vendor
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.IntentSender
|
||||
import android.content.pm.IPackageInstaller
|
||||
import android.content.pm.IPackageInstallerSession
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.os.Process
|
||||
import io.nekohasekai.sfa.vendor.hidden.IPackageManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
||||
import rikka.shizuku.Shizuku
|
||||
import rikka.shizuku.ShizukuBinderWrapper
|
||||
import rikka.shizuku.SystemServiceHelper
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import android.content.IIntentSender
|
||||
|
||||
object ShizukuInstaller {
|
||||
|
||||
@@ -59,127 +46,11 @@ object ShizukuInstaller {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPackageInstaller(): IPackageInstaller {
|
||||
val packageManagerBinder = SystemServiceHelper.getSystemService("package")
|
||||
val packageManager = IPackageManager.Stub.asInterface(ShizukuBinderWrapper(packageManagerBinder))
|
||||
val installerBinder = packageManager.packageInstaller.asBinder()
|
||||
return IPackageInstaller.Stub.asInterface(ShizukuBinderWrapper(installerBinder))
|
||||
}
|
||||
|
||||
private fun createPackageInstaller(
|
||||
installer: IPackageInstaller,
|
||||
installerPackageName: String,
|
||||
installerAttributionTag: String?,
|
||||
userId: Int
|
||||
): PackageInstaller {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
return PackageInstaller::class.java
|
||||
.getConstructor(
|
||||
IPackageInstaller::class.java,
|
||||
String::class.java,
|
||||
String::class.java,
|
||||
Int::class.javaPrimitiveType
|
||||
)
|
||||
.newInstance(installer, installerPackageName, installerAttributionTag, userId)
|
||||
} else {
|
||||
return PackageInstaller::class.java
|
||||
.getConstructor(
|
||||
IPackageInstaller::class.java,
|
||||
String::class.java,
|
||||
Int::class.javaPrimitiveType
|
||||
)
|
||||
.newInstance(installer, installerPackageName, userId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSession(session: IPackageInstallerSession): PackageInstaller.Session {
|
||||
return PackageInstaller.Session::class.java
|
||||
.getConstructor(IPackageInstallerSession::class.java)
|
||||
.newInstance(session)
|
||||
}
|
||||
|
||||
private fun createIntentSender(onResult: (Intent) -> Unit): IntentSender {
|
||||
val sender = object : IIntentSender.Stub() {
|
||||
override fun send(
|
||||
code: Int,
|
||||
intent: Intent,
|
||||
resolvedType: String?,
|
||||
whitelistToken: android.os.IBinder?,
|
||||
finishedReceiver: android.content.IIntentReceiver?,
|
||||
requiredPermission: String?,
|
||||
options: android.os.Bundle?
|
||||
) {
|
||||
onResult(intent)
|
||||
}
|
||||
}
|
||||
return IntentSender::class.java
|
||||
.getConstructor(IIntentSender::class.java)
|
||||
.newInstance(sender)
|
||||
}
|
||||
|
||||
suspend fun install(apkFile: File): Result<Unit> = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
HiddenApiBypass.addHiddenApiExemptions("")
|
||||
}
|
||||
|
||||
val iPackageInstaller = getPackageInstaller()
|
||||
val isRoot = isRunningAsRoot()
|
||||
|
||||
val installerPackageName = if (isRoot) "io.nekohasekai.sfa" else "com.android.shell"
|
||||
val installerAttributionTag: String? = null
|
||||
val userId = if (isRoot) Process.myUserHandle().hashCode() else 0
|
||||
|
||||
val packageInstaller = createPackageInstaller(
|
||||
iPackageInstaller,
|
||||
installerPackageName,
|
||||
installerAttributionTag,
|
||||
userId
|
||||
)
|
||||
|
||||
val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
|
||||
val sessionId = packageInstaller.createSession(params)
|
||||
|
||||
val iSession = IPackageInstallerSession.Stub.asInterface(
|
||||
ShizukuBinderWrapper(iPackageInstaller.openSession(sessionId).asBinder())
|
||||
)
|
||||
val session = createSession(iSession)
|
||||
|
||||
try {
|
||||
FileInputStream(apkFile).use { inputStream ->
|
||||
session.openWrite("base.apk", 0, apkFile.length()).use { outputStream ->
|
||||
inputStream.copyTo(outputStream)
|
||||
session.fsync(outputStream)
|
||||
}
|
||||
}
|
||||
|
||||
val resultIntent = arrayOfNulls<Intent>(1)
|
||||
val latch = CountDownLatch(1)
|
||||
|
||||
val intentSender = createIntentSender { intent ->
|
||||
resultIntent[0] = intent
|
||||
latch.countDown()
|
||||
}
|
||||
|
||||
session.commit(intentSender)
|
||||
latch.await(60, TimeUnit.SECONDS)
|
||||
|
||||
val intent = resultIntent[0]
|
||||
?: return@withContext Result.failure(Exception("Installation timed out"))
|
||||
|
||||
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE)
|
||||
val message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
|
||||
|
||||
if (status == PackageInstaller.STATUS_SUCCESS) {
|
||||
Result.success(Unit)
|
||||
} else {
|
||||
Result.failure(Exception("Installation failed: $status - $message"))
|
||||
}
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
suspend fun install(apkFile: File) = withContext(Dispatchers.IO) {
|
||||
val service = ShizukuPrivilegedServiceClient.getService()
|
||||
val userId = if (isRunningAsRoot()) Process.myUserHandle().hashCode() else 0
|
||||
ParcelFileDescriptor.open(apkFile, ParcelFileDescriptor.MODE_READ_ONLY).use { pfd ->
|
||||
service.installPackage(pfd, apkFile.length(), userId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,13 @@ package io.nekohasekai.sfa.vendor
|
||||
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.Process
|
||||
import io.nekohasekai.sfa.Application
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import rikka.shizuku.Shizuku
|
||||
import rikka.shizuku.ShizukuBinderWrapper
|
||||
import rikka.shizuku.SystemServiceHelper
|
||||
|
||||
object ShizukuPackageManager {
|
||||
|
||||
@@ -33,19 +31,19 @@ object ShizukuPackageManager {
|
||||
private val binderDeadListener = Shizuku.OnBinderDeadListener {
|
||||
_binderReady.value = false
|
||||
_permissionGranted.value = false
|
||||
ShizukuPrivilegedServiceClient.reset()
|
||||
}
|
||||
|
||||
private val permissionResultListener = Shizuku.OnRequestPermissionResultListener { _, grantResult ->
|
||||
_permissionGranted.value = grantResult == PackageManager.PERMISSION_GRANTED
|
||||
_binderReady.value = isAvailable()
|
||||
}
|
||||
|
||||
fun registerListeners() {
|
||||
Shizuku.addBinderReceivedListenerSticky(binderReceivedListener)
|
||||
Shizuku.addBinderDeadListener(binderDeadListener)
|
||||
Shizuku.addRequestPermissionResultListener(permissionResultListener)
|
||||
_shizukuInstalled.value = isShizukuInstalled()
|
||||
_binderReady.value = isAvailable()
|
||||
_permissionGranted.value = checkPermission()
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun isShizukuInstalled(): Boolean {
|
||||
@@ -69,46 +67,17 @@ object ShizukuPackageManager {
|
||||
|
||||
fun requestPermission() = ShizukuInstaller.requestPermission()
|
||||
|
||||
fun getInstalledPackages(flags: Int): List<PackageInfo> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
HiddenApiBypass.addHiddenApiExemptions("")
|
||||
}
|
||||
|
||||
val packageManagerBinder = SystemServiceHelper.getSystemService("package")
|
||||
val wrappedBinder = ShizukuBinderWrapper(packageManagerBinder)
|
||||
|
||||
val iPackageManagerClass = Class.forName("android.content.pm.IPackageManager")
|
||||
val stubClass = Class.forName("android.content.pm.IPackageManager\$Stub")
|
||||
val asInterfaceMethod = stubClass.getMethod("asInterface", IBinder::class.java)
|
||||
val iPackageManager = asInterfaceMethod.invoke(null, wrappedBinder)
|
||||
|
||||
val userId = android.os.Process.myUserHandle().hashCode()
|
||||
|
||||
val result = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
val method = iPackageManagerClass.getMethod(
|
||||
"getInstalledPackages",
|
||||
Long::class.javaPrimitiveType,
|
||||
Int::class.javaPrimitiveType
|
||||
)
|
||||
method.invoke(iPackageManager, flags.toLong(), userId)
|
||||
} else {
|
||||
val method = iPackageManagerClass.getMethod(
|
||||
"getInstalledPackages",
|
||||
Int::class.javaPrimitiveType,
|
||||
Int::class.javaPrimitiveType
|
||||
)
|
||||
method.invoke(iPackageManager, flags, userId)
|
||||
}
|
||||
|
||||
return extractPackageList(result)
|
||||
fun refresh() {
|
||||
_shizukuInstalled.value = isShizukuInstalled()
|
||||
_binderReady.value = isAvailable()
|
||||
_permissionGranted.value = checkPermission()
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun extractPackageList(parceledListSlice: Any?): List<PackageInfo> {
|
||||
if (parceledListSlice == null) return emptyList()
|
||||
|
||||
val getListMethod = parceledListSlice.javaClass.getMethod("getList")
|
||||
val list = getListMethod.invoke(parceledListSlice) as? List<*>
|
||||
return list?.filterIsInstance<PackageInfo>() ?: emptyList()
|
||||
suspend fun getInstalledPackages(flags: Int): List<PackageInfo> = withContext(Dispatchers.IO) {
|
||||
val service = ShizukuPrivilegedServiceClient.getService()
|
||||
val userId = Process.myUserHandle().hashCode()
|
||||
val slice = service.getInstalledPackages(flags, userId)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
slice.list as List<PackageInfo>
|
||||
}
|
||||
}
|
||||
|
||||
24
app/src/minApi23/java/io/nekohasekai/sfa/vendor/ShizukuPrivilegedService.kt
vendored
Normal file
24
app/src/minApi23/java/io/nekohasekai/sfa/vendor/ShizukuPrivilegedService.kt
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
package io.nekohasekai.sfa.vendor
|
||||
|
||||
import android.content.pm.PackageInfo
|
||||
import android.os.ParcelFileDescriptor
|
||||
import io.nekohasekai.sfa.bg.IShizukuService
|
||||
import io.nekohasekai.sfa.bg.ParceledListSlice
|
||||
import java.io.IOException
|
||||
|
||||
class ShizukuPrivilegedService : IShizukuService.Stub() {
|
||||
|
||||
override fun destroy() {
|
||||
System.exit(0)
|
||||
}
|
||||
|
||||
override fun getInstalledPackages(flags: Int, userId: Int): ParceledListSlice<PackageInfo> {
|
||||
val allPackages = PrivilegedServiceUtils.getInstalledPackages(flags, userId)
|
||||
return ParceledListSlice(allPackages)
|
||||
}
|
||||
|
||||
override fun installPackage(apk: ParcelFileDescriptor?, size: Long, userId: Int) {
|
||||
if (apk == null) throw IOException("APK file descriptor is null")
|
||||
PrivilegedServiceUtils.installPackage(apk, size, userId)
|
||||
}
|
||||
}
|
||||
86
app/src/minApi23/java/io/nekohasekai/sfa/vendor/ShizukuPrivilegedServiceClient.kt
vendored
Normal file
86
app/src/minApi23/java/io/nekohasekai/sfa/vendor/ShizukuPrivilegedServiceClient.kt
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
package io.nekohasekai.sfa.vendor
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.ServiceConnection
|
||||
import android.os.IBinder
|
||||
import io.nekohasekai.sfa.Application
|
||||
import io.nekohasekai.sfa.BuildConfig
|
||||
import io.nekohasekai.sfa.bg.IShizukuService
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import rikka.shizuku.Shizuku
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
object ShizukuPrivilegedServiceClient {
|
||||
|
||||
private val serviceMutex = Mutex()
|
||||
private var service: IShizukuService? = null
|
||||
private var connection: ServiceConnection? = null
|
||||
|
||||
private val args = Shizuku.UserServiceArgs(
|
||||
ComponentName(Application.application, ShizukuPrivilegedService::class.java)
|
||||
)
|
||||
.tag("sfa-privileged")
|
||||
.processNameSuffix("privileged")
|
||||
.version(BuildConfig.VERSION_CODE)
|
||||
.debuggable(BuildConfig.DEBUG)
|
||||
|
||||
suspend fun getService(): IShizukuService = serviceMutex.withLock {
|
||||
service?.let { return it }
|
||||
return withContext(Dispatchers.Main) {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
val conn = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
|
||||
val svc = if (binder != null && binder.pingBinder()) {
|
||||
IShizukuService.Stub.asInterface(binder)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
if (svc == null) {
|
||||
continuation.resumeWithException(IllegalStateException("Invalid Shizuku service binder"))
|
||||
return
|
||||
}
|
||||
service = svc
|
||||
connection = this
|
||||
continuation.resume(svc)
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
service = null
|
||||
connection = null
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Shizuku.bindUserService(args, conn)
|
||||
} catch (e: Throwable) {
|
||||
continuation.resumeWithException(e)
|
||||
return@suspendCancellableCoroutine
|
||||
}
|
||||
|
||||
continuation.invokeOnCancellation {
|
||||
try {
|
||||
Shizuku.unbindUserService(args, conn, false)
|
||||
} catch (_: Throwable) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
val conn = connection ?: return
|
||||
service = null
|
||||
connection = null
|
||||
try {
|
||||
Shizuku.unbindUserService(args, conn, false)
|
||||
} catch (_: Throwable) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package io.nekohasekai.sfa.vendor.hidden;
|
||||
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.os.IInterface;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import android.content.pm.IPackageInstaller;
|
||||
|
||||
public interface IPackageManager extends IInterface {
|
||||
|
||||
IPackageInstaller getPackageInstaller() throws RemoteException;
|
||||
|
||||
abstract class Stub extends Binder implements IPackageManager {
|
||||
public static IPackageManager asInterface(IBinder binder) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user