Add alternative support for QUERY_ALL_PACKAGES in play flavor
This commit is contained in:
101
app/src/minApi23/java/io/nekohasekai/sfa/vendor/PackageQueryManager.kt
vendored
Normal file
101
app/src/minApi23/java/io/nekohasekai/sfa/vendor/PackageQueryManager.kt
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
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.database.Settings
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
class PrivilegedAccessRequiredException(message: String) : Exception(message)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
private val _queryMode = MutableStateFlow(Settings.perAppProxyPackageQueryMode)
|
||||
val queryMode: StateFlow<String> = _queryMode
|
||||
|
||||
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
|
||||
|
||||
fun isShizukuAvailable(): Boolean =
|
||||
ShizukuPackageManager.isAvailable() && ShizukuPackageManager.checkPermission()
|
||||
|
||||
fun registerListeners() {
|
||||
ShizukuPackageManager.registerListeners()
|
||||
_queryMode.value = Settings.perAppProxyPackageQueryMode
|
||||
}
|
||||
|
||||
fun unregisterListeners() {
|
||||
ShizukuPackageManager.unregisterListeners()
|
||||
}
|
||||
|
||||
fun requestShizukuPermission() {
|
||||
ShizukuPackageManager.requestPermission()
|
||||
}
|
||||
|
||||
suspend fun checkRootAvailable(): Boolean {
|
||||
return RootPackageManager.checkRootAvailable()
|
||||
}
|
||||
|
||||
fun setQueryMode(mode: String) {
|
||||
_queryMode.value = mode
|
||||
}
|
||||
|
||||
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 RootPackageManager.getInstalledPackages(flags)
|
||||
}
|
||||
|
||||
if (!isShizukuAvailable()) {
|
||||
throw PrivilegedAccessRequiredException("Shizuku access required")
|
||||
}
|
||||
return ShizukuPackageManager.getInstalledPackages(flags)
|
||||
}
|
||||
|
||||
private fun getPackagesViaPackageManager(flags: Int): List<PackageInfo> {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
Application.packageManager.getInstalledPackages(
|
||||
PackageManager.PackageInfoFlags.of(flags.toLong())
|
||||
)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
Application.packageManager.getInstalledPackages(flags)
|
||||
}
|
||||
}
|
||||
}
|
||||
98
app/src/minApi23/java/io/nekohasekai/sfa/vendor/RootPackageManager.kt
vendored
Normal file
98
app/src/minApi23/java/io/nekohasekai/sfa/vendor/RootPackageManager.kt
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getInstalledPackages(flags: Int): List<PackageInfo> {
|
||||
val svc = bindService()
|
||||
return svc.getInstalledPackages(flags)
|
||||
}
|
||||
}
|
||||
28
app/src/minApi23/java/io/nekohasekai/sfa/vendor/RootPackageManagerService.kt
vendored
Normal file
28
app/src/minApi23/java/io/nekohasekai/sfa/vendor/RootPackageManagerService.kt
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
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): List<PackageInfo> {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
packageManager.getInstalledPackages(
|
||||
PackageManager.PackageInfoFlags.of(flags.toLong())
|
||||
)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
packageManager.getInstalledPackages(flags)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder {
|
||||
return binder
|
||||
}
|
||||
}
|
||||
114
app/src/minApi23/java/io/nekohasekai/sfa/vendor/ShizukuPackageManager.kt
vendored
Normal file
114
app/src/minApi23/java/io/nekohasekai/sfa/vendor/ShizukuPackageManager.kt
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
package io.nekohasekai.sfa.vendor
|
||||
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import io.nekohasekai.sfa.Application
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
||||
import rikka.shizuku.Shizuku
|
||||
import rikka.shizuku.ShizukuBinderWrapper
|
||||
import rikka.shizuku.SystemServiceHelper
|
||||
|
||||
object ShizukuPackageManager {
|
||||
|
||||
private const val SHIZUKU_PACKAGE = "moe.shizuku.privileged.api"
|
||||
|
||||
private val _shizukuInstalled = MutableStateFlow(false)
|
||||
val shizukuInstalled: StateFlow<Boolean> = _shizukuInstalled
|
||||
|
||||
private val _binderReady = MutableStateFlow(false)
|
||||
val binderReady: StateFlow<Boolean> = _binderReady
|
||||
|
||||
private val _permissionGranted = MutableStateFlow(false)
|
||||
val permissionGranted: StateFlow<Boolean> = _permissionGranted
|
||||
|
||||
private val binderReceivedListener = Shizuku.OnBinderReceivedListener {
|
||||
_binderReady.value = true
|
||||
_permissionGranted.value = checkPermission()
|
||||
}
|
||||
|
||||
private val binderDeadListener = Shizuku.OnBinderDeadListener {
|
||||
_binderReady.value = false
|
||||
_permissionGranted.value = false
|
||||
}
|
||||
|
||||
private val permissionResultListener = Shizuku.OnRequestPermissionResultListener { _, grantResult ->
|
||||
_permissionGranted.value = grantResult == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
fun registerListeners() {
|
||||
Shizuku.addBinderReceivedListenerSticky(binderReceivedListener)
|
||||
Shizuku.addBinderDeadListener(binderDeadListener)
|
||||
Shizuku.addRequestPermissionResultListener(permissionResultListener)
|
||||
_shizukuInstalled.value = isShizukuInstalled()
|
||||
_binderReady.value = isAvailable()
|
||||
_permissionGranted.value = checkPermission()
|
||||
}
|
||||
|
||||
fun isShizukuInstalled(): Boolean {
|
||||
return try {
|
||||
Application.packageManager.getPackageInfo(SHIZUKU_PACKAGE, 0)
|
||||
true
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun unregisterListeners() {
|
||||
Shizuku.removeBinderReceivedListener(binderReceivedListener)
|
||||
Shizuku.removeBinderDeadListener(binderDeadListener)
|
||||
Shizuku.removeRequestPermissionResultListener(permissionResultListener)
|
||||
}
|
||||
|
||||
fun isAvailable(): Boolean = ShizukuInstaller.isAvailable()
|
||||
|
||||
fun checkPermission(): Boolean = ShizukuInstaller.checkPermission()
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user