Fix per-app proxy managed mode

This commit is contained in:
世界
2026-02-02 16:27:29 +08:00
parent fa538568d4
commit c4ec53edbb
6 changed files with 73 additions and 110 deletions

View File

@@ -3,14 +3,11 @@ package io.nekohasekai.sfa.bg
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import android.widget.Toast
import io.nekohasekai.sfa.R
import io.nekohasekai.sfa.compose.screen.profileoverride.PerAppProxyScanner
import io.nekohasekai.sfa.database.Settings
import io.nekohasekai.sfa.vendor.PackageQueryManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -48,29 +45,7 @@ class AppChangeReceiver : BroadcastReceiver() {
private suspend fun rescanAllApps() {
Log.d(TAG, "rescanning all apps")
val packageManagerFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
PackageManager.MATCH_UNINSTALLED_PACKAGES or
PackageManager.GET_ACTIVITIES or PackageManager.GET_SERVICES or
PackageManager.GET_RECEIVERS or PackageManager.GET_PROVIDERS
} else {
@Suppress("DEPRECATION")
PackageManager.GET_UNINSTALLED_PACKAGES or
PackageManager.GET_ACTIVITIES or PackageManager.GET_SERVICES or
PackageManager.GET_RECEIVERS or PackageManager.GET_PROVIDERS
}
val retryFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
PackageManager.MATCH_UNINSTALLED_PACKAGES or PackageManager.GET_PERMISSIONS
} else {
@Suppress("DEPRECATION")
PackageManager.GET_UNINSTALLED_PACKAGES or PackageManager.GET_PERMISSIONS
}
val installedPackages = PackageQueryManager.getInstalledPackages(packageManagerFlags, retryFlags)
val chinaApps = mutableSetOf<String>()
for (packageInfo in installedPackages) {
if (PerAppProxyScanner.scanChinaPackage(packageInfo)) {
chinaApps.add(packageInfo.packageName)
}
}
val chinaApps = PerAppProxyScanner.scanAllChinaApps()
Settings.perAppProxyManagedList = chinaApps
Log.d(TAG, "rescan complete, found ${chinaApps.size} china apps")
}

View File

@@ -140,7 +140,7 @@ class BoxService(private val service: Service, private val platformInterface: Pl
autoRedirect = Settings.autoRedirect
if (Vendor.isPerAppProxyAvailable() && Settings.perAppProxyEnabled) {
val appList = Settings.getEffectivePerAppProxyList()
if (Settings.perAppProxyMode == Settings.PER_APP_PROXY_INCLUDE) {
if (Settings.getEffectivePerAppProxyMode() == Settings.PER_APP_PROXY_INCLUDE) {
includePackage =
PlatformInterfaceWrapper.StringArray(appList.iterator())
} else {
@@ -223,7 +223,7 @@ class BoxService(private val service: Service, private val platformInterface: Pl
autoRedirect = Settings.autoRedirect
if (Vendor.isPerAppProxyAvailable() && Settings.perAppProxyEnabled) {
val appList = Settings.getEffectivePerAppProxyList()
if (Settings.perAppProxyMode == Settings.PER_APP_PROXY_INCLUDE) {
if (Settings.getEffectivePerAppProxyMode() == Settings.PER_APP_PROXY_INCLUDE) {
includePackage = PlatformInterfaceWrapper.StringArray(appList.iterator())
} else {
excludePackage = PlatformInterfaceWrapper.StringArray(appList.iterator())

View File

@@ -6,6 +6,7 @@ import android.net.ProxyInfo
import android.net.VpnService
import android.os.Build
import android.os.IBinder
import android.util.Log
import io.nekohasekai.libbox.Notification
import io.nekohasekai.libbox.TunOptions
import io.nekohasekai.sfa.database.Settings
@@ -130,8 +131,11 @@ class VPNService :
if (includePackage.hasNext()) {
while (includePackage.hasNext()) {
try {
builder.addAllowedApplication(includePackage.next())
} catch (_: NameNotFoundException) {
val nextPackage = includePackage.next()
builder.addAllowedApplication(nextPackage)
Log.d("VPNService", "addAllowedApplication: $nextPackage")
} catch (e: NameNotFoundException) {
Log.e("VPNService", "addAllowedApplication failed", e)
}
}
}
@@ -140,8 +144,11 @@ class VPNService :
if (excludePackage.hasNext()) {
while (excludePackage.hasNext()) {
try {
builder.addDisallowedApplication(excludePackage.next())
} catch (_: NameNotFoundException) {
val nextPackage = excludePackage.next()
builder.addDisallowedApplication(nextPackage)
Log.d("VPNService", "addDisallowedApplication: $nextPackage")
} catch (e: NameNotFoundException) {
Log.e("VPNService", "addDisallowedApplication failed", e)
}
}
}

View File

@@ -3,6 +3,7 @@ package io.nekohasekai.sfa.compose.screen.profileoverride
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Build
import io.nekohasekai.sfa.Application
import android.util.Log
import android.widget.Toast
import androidx.compose.animation.AnimatedVisibility
@@ -1275,8 +1276,40 @@ object PerAppProxyScanner {
("(" + chinaAppPrefixList.joinToString("|").replace(".", "\\.") + ").*").toRegex()
}
suspend fun scanAllChinaApps(): Set<String> = withContext(Dispatchers.Default) {
val packageManagerFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
PackageManager.MATCH_UNINSTALLED_PACKAGES or
PackageManager.GET_ACTIVITIES or PackageManager.GET_SERVICES or
PackageManager.GET_RECEIVERS or PackageManager.GET_PROVIDERS
} else {
@Suppress("DEPRECATION")
PackageManager.GET_UNINSTALLED_PACKAGES or
PackageManager.GET_ACTIVITIES or PackageManager.GET_SERVICES or
PackageManager.GET_RECEIVERS or PackageManager.GET_PROVIDERS
}
val retryFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
PackageManager.MATCH_UNINSTALLED_PACKAGES or PackageManager.GET_PERMISSIONS
} else {
@Suppress("DEPRECATION")
PackageManager.GET_UNINSTALLED_PACKAGES or PackageManager.GET_PERMISSIONS
}
val installedPackages = PackageQueryManager.getInstalledPackages(packageManagerFlags, retryFlags)
val chinaApps = mutableSetOf<String>()
installedPackages.map { packageInfo ->
async {
if (scanChinaPackage(packageInfo)) {
synchronized(chinaApps) {
chinaApps.add(packageInfo.packageName)
}
}
}
}.awaitAll()
chinaApps.toSet()
}
fun scanChinaPackage(packageInfo: PackageInfo): Boolean {
val packageName = packageInfo.packageName
if (packageName == Application.application.packageName) return false
skipPrefixList.forEach {
if (packageName == it || packageName.startsWith("$it.")) return false
}

View File

@@ -1,9 +1,7 @@
package io.nekohasekai.sfa.compose.screen.settings
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@@ -64,8 +62,6 @@ import io.nekohasekai.sfa.compose.topbar.OverrideTopBar
import io.nekohasekai.sfa.database.Settings
import io.nekohasekai.sfa.vendor.PackageQueryManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -93,6 +89,18 @@ fun ProfileOverrideScreen(navController: NavController) {
var perAppProxyEnabled by remember { mutableStateOf(Settings.perAppProxyEnabled) }
var managedModeEnabled by remember { mutableStateOf(Settings.perAppProxyManagedMode) }
var isScanning by remember { mutableStateOf(false) }
fun scanAndSaveManagedList() {
isScanning = true
scope.launch {
val chinaApps = PerAppProxyScanner.scanAllChinaApps()
withContext(Dispatchers.IO) {
Settings.perAppProxyManagedList = chinaApps
}
isScanning = false
}
}
var showShizukuDialog by remember { mutableStateOf(false) }
var showRootDialog by remember { mutableStateOf(false) }
var showModeDialog by remember { mutableStateOf(false) }
@@ -150,12 +158,7 @@ fun ProfileOverrideScreen(navController: NavController) {
Settings.perAppProxyEnabled = true
}
if (managedModeEnabled) {
isScanning = true
val chinaApps = scanAllChinaApps()
withContext(Dispatchers.IO) {
Settings.perAppProxyManagedList = chinaApps
}
isScanning = false
scanAndSaveManagedList()
}
}
}
@@ -352,14 +355,7 @@ fun ProfileOverrideScreen(navController: NavController) {
Settings.perAppProxyEnabled = checked
}
if (checked && managedModeEnabled) {
isScanning = true
scope.launch {
val chinaApps = scanAllChinaApps()
withContext(Dispatchers.IO) {
Settings.perAppProxyManagedList = chinaApps
}
isScanning = false
}
scanAndSaveManagedList()
}
}
},
@@ -465,18 +461,10 @@ fun ProfileOverrideScreen(navController: NavController) {
onCheckedChange = { checked ->
if (checked) {
managedModeEnabled = true
isScanning = true
scope.launch {
withContext(Dispatchers.IO) {
Settings.perAppProxyManagedMode = true
Settings.perAppProxyMode = Settings.PER_APP_PROXY_EXCLUDE
}
val chinaApps = scanAllChinaApps()
withContext(Dispatchers.IO) {
Settings.perAppProxyManagedList = chinaApps
}
isScanning = false
scope.launch(Dispatchers.IO) {
Settings.perAppProxyManagedMode = true
}
scanAndSaveManagedList()
} else {
managedModeEnabled = false
scope.launch(Dispatchers.IO) {
@@ -518,14 +506,7 @@ fun ProfileOverrideScreen(navController: NavController) {
Settings.perAppProxyEnabled = true
}
if (managedModeEnabled) {
isScanning = true
scope.launch {
val chinaApps = scanAllChinaApps()
withContext(Dispatchers.IO) {
Settings.perAppProxyManagedList = chinaApps
}
isScanning = false
}
scanAndSaveManagedList()
}
},
) {
@@ -601,12 +582,7 @@ fun ProfileOverrideScreen(navController: NavController) {
Settings.perAppProxyEnabled = true
}
if (managedModeEnabled) {
isScanning = true
val chinaApps = scanAllChinaApps()
withContext(Dispatchers.IO) {
Settings.perAppProxyManagedList = chinaApps
}
isScanning = false
scanAndSaveManagedList()
}
} else {
showRootDialog = false
@@ -694,37 +670,3 @@ fun ProfileOverrideScreen(navController: NavController) {
}
}
}
private suspend fun scanAllChinaApps(): Set<String> = withContext(Dispatchers.Default) {
val packageManagerFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
PackageManager.MATCH_UNINSTALLED_PACKAGES or
PackageManager.GET_ACTIVITIES or PackageManager.GET_SERVICES or
PackageManager.GET_RECEIVERS or PackageManager.GET_PROVIDERS
} else {
@Suppress("DEPRECATION")
PackageManager.GET_UNINSTALLED_PACKAGES or
PackageManager.GET_ACTIVITIES or PackageManager.GET_SERVICES or
PackageManager.GET_RECEIVERS or PackageManager.GET_PROVIDERS
}
val retryFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
PackageManager.MATCH_UNINSTALLED_PACKAGES or PackageManager.GET_PERMISSIONS
} else {
@Suppress("DEPRECATION")
PackageManager.GET_UNINSTALLED_PACKAGES or PackageManager.GET_PERMISSIONS
}
val installedPackages = PackageQueryManager.getInstalledPackages(packageManagerFlags, retryFlags)
val chinaApps = mutableSetOf<String>()
installedPackages.map { packageInfo ->
async {
if (PerAppProxyScanner.scanChinaPackage(packageInfo)) {
synchronized(chinaApps) {
chinaApps.add(packageInfo.packageName)
}
}
}
}.awaitAll()
chinaApps.toSet()
}

View File

@@ -82,8 +82,14 @@ object Settings {
const val PACKAGE_QUERY_MODE_ROOT = "ROOT"
var perAppProxyPackageQueryMode by dataStore.string(SettingsKey.PER_APP_PROXY_PACKAGE_QUERY_MODE) { PACKAGE_QUERY_MODE_SHIZUKU }
fun getEffectivePerAppProxyMode(): Int = if (perAppProxyManagedMode) {
PER_APP_PROXY_EXCLUDE
} else {
perAppProxyMode
}
fun getEffectivePerAppProxyList(): Set<String> = if (perAppProxyManagedMode) {
perAppProxyList union perAppProxyManagedList
perAppProxyManagedList
} else {
perAppProxyList
}