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.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import io.nekohasekai.sfa.R import io.nekohasekai.sfa.R
import io.nekohasekai.sfa.compose.screen.profileoverride.PerAppProxyScanner import io.nekohasekai.sfa.compose.screen.profileoverride.PerAppProxyScanner
import io.nekohasekai.sfa.database.Settings import io.nekohasekai.sfa.database.Settings
import io.nekohasekai.sfa.vendor.PackageQueryManager
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -48,29 +45,7 @@ class AppChangeReceiver : BroadcastReceiver() {
private suspend fun rescanAllApps() { private suspend fun rescanAllApps() {
Log.d(TAG, "rescanning all apps") Log.d(TAG, "rescanning all apps")
val packageManagerFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { val chinaApps = PerAppProxyScanner.scanAllChinaApps()
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)
}
}
Settings.perAppProxyManagedList = chinaApps Settings.perAppProxyManagedList = chinaApps
Log.d(TAG, "rescan complete, found ${chinaApps.size} china apps") 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 autoRedirect = Settings.autoRedirect
if (Vendor.isPerAppProxyAvailable() && Settings.perAppProxyEnabled) { if (Vendor.isPerAppProxyAvailable() && Settings.perAppProxyEnabled) {
val appList = Settings.getEffectivePerAppProxyList() val appList = Settings.getEffectivePerAppProxyList()
if (Settings.perAppProxyMode == Settings.PER_APP_PROXY_INCLUDE) { if (Settings.getEffectivePerAppProxyMode() == Settings.PER_APP_PROXY_INCLUDE) {
includePackage = includePackage =
PlatformInterfaceWrapper.StringArray(appList.iterator()) PlatformInterfaceWrapper.StringArray(appList.iterator())
} else { } else {
@@ -223,7 +223,7 @@ class BoxService(private val service: Service, private val platformInterface: Pl
autoRedirect = Settings.autoRedirect autoRedirect = Settings.autoRedirect
if (Vendor.isPerAppProxyAvailable() && Settings.perAppProxyEnabled) { if (Vendor.isPerAppProxyAvailable() && Settings.perAppProxyEnabled) {
val appList = Settings.getEffectivePerAppProxyList() 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()) includePackage = PlatformInterfaceWrapper.StringArray(appList.iterator())
} else { } else {
excludePackage = PlatformInterfaceWrapper.StringArray(appList.iterator()) excludePackage = PlatformInterfaceWrapper.StringArray(appList.iterator())

View File

@@ -6,6 +6,7 @@ import android.net.ProxyInfo
import android.net.VpnService import android.net.VpnService
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.util.Log
import io.nekohasekai.libbox.Notification import io.nekohasekai.libbox.Notification
import io.nekohasekai.libbox.TunOptions import io.nekohasekai.libbox.TunOptions
import io.nekohasekai.sfa.database.Settings import io.nekohasekai.sfa.database.Settings
@@ -130,8 +131,11 @@ class VPNService :
if (includePackage.hasNext()) { if (includePackage.hasNext()) {
while (includePackage.hasNext()) { while (includePackage.hasNext()) {
try { try {
builder.addAllowedApplication(includePackage.next()) val nextPackage = includePackage.next()
} catch (_: NameNotFoundException) { 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()) { if (excludePackage.hasNext()) {
while (excludePackage.hasNext()) { while (excludePackage.hasNext()) {
try { try {
builder.addDisallowedApplication(excludePackage.next()) val nextPackage = excludePackage.next()
} catch (_: NameNotFoundException) { 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.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build import android.os.Build
import io.nekohasekai.sfa.Application
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
@@ -1275,8 +1276,40 @@ object PerAppProxyScanner {
("(" + chinaAppPrefixList.joinToString("|").replace(".", "\\.") + ").*").toRegex() ("(" + 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 { fun scanChinaPackage(packageInfo: PackageInfo): Boolean {
val packageName = packageInfo.packageName val packageName = packageInfo.packageName
if (packageName == Application.application.packageName) return false
skipPrefixList.forEach { skipPrefixList.forEach {
if (packageName == it || packageName.startsWith("$it.")) return false if (packageName == it || packageName.startsWith("$it.")) return false
} }

View File

@@ -1,9 +1,7 @@
package io.nekohasekai.sfa.compose.screen.settings package io.nekohasekai.sfa.compose.screen.settings
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Build
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable 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.database.Settings
import io.nekohasekai.sfa.vendor.PackageQueryManager import io.nekohasekai.sfa.vendor.PackageQueryManager
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -93,6 +89,18 @@ fun ProfileOverrideScreen(navController: NavController) {
var perAppProxyEnabled by remember { mutableStateOf(Settings.perAppProxyEnabled) } var perAppProxyEnabled by remember { mutableStateOf(Settings.perAppProxyEnabled) }
var managedModeEnabled by remember { mutableStateOf(Settings.perAppProxyManagedMode) } var managedModeEnabled by remember { mutableStateOf(Settings.perAppProxyManagedMode) }
var isScanning by remember { mutableStateOf(false) } 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 showShizukuDialog by remember { mutableStateOf(false) }
var showRootDialog by remember { mutableStateOf(false) } var showRootDialog by remember { mutableStateOf(false) }
var showModeDialog by remember { mutableStateOf(false) } var showModeDialog by remember { mutableStateOf(false) }
@@ -150,12 +158,7 @@ fun ProfileOverrideScreen(navController: NavController) {
Settings.perAppProxyEnabled = true Settings.perAppProxyEnabled = true
} }
if (managedModeEnabled) { if (managedModeEnabled) {
isScanning = true scanAndSaveManagedList()
val chinaApps = scanAllChinaApps()
withContext(Dispatchers.IO) {
Settings.perAppProxyManagedList = chinaApps
}
isScanning = false
} }
} }
} }
@@ -352,14 +355,7 @@ fun ProfileOverrideScreen(navController: NavController) {
Settings.perAppProxyEnabled = checked Settings.perAppProxyEnabled = checked
} }
if (checked && managedModeEnabled) { if (checked && managedModeEnabled) {
isScanning = true scanAndSaveManagedList()
scope.launch {
val chinaApps = scanAllChinaApps()
withContext(Dispatchers.IO) {
Settings.perAppProxyManagedList = chinaApps
}
isScanning = false
}
} }
} }
}, },
@@ -465,18 +461,10 @@ fun ProfileOverrideScreen(navController: NavController) {
onCheckedChange = { checked -> onCheckedChange = { checked ->
if (checked) { if (checked) {
managedModeEnabled = true managedModeEnabled = true
isScanning = true scope.launch(Dispatchers.IO) {
scope.launch { Settings.perAppProxyManagedMode = true
withContext(Dispatchers.IO) {
Settings.perAppProxyManagedMode = true
Settings.perAppProxyMode = Settings.PER_APP_PROXY_EXCLUDE
}
val chinaApps = scanAllChinaApps()
withContext(Dispatchers.IO) {
Settings.perAppProxyManagedList = chinaApps
}
isScanning = false
} }
scanAndSaveManagedList()
} else { } else {
managedModeEnabled = false managedModeEnabled = false
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
@@ -518,14 +506,7 @@ fun ProfileOverrideScreen(navController: NavController) {
Settings.perAppProxyEnabled = true Settings.perAppProxyEnabled = true
} }
if (managedModeEnabled) { if (managedModeEnabled) {
isScanning = true scanAndSaveManagedList()
scope.launch {
val chinaApps = scanAllChinaApps()
withContext(Dispatchers.IO) {
Settings.perAppProxyManagedList = chinaApps
}
isScanning = false
}
} }
}, },
) { ) {
@@ -601,12 +582,7 @@ fun ProfileOverrideScreen(navController: NavController) {
Settings.perAppProxyEnabled = true Settings.perAppProxyEnabled = true
} }
if (managedModeEnabled) { if (managedModeEnabled) {
isScanning = true scanAndSaveManagedList()
val chinaApps = scanAllChinaApps()
withContext(Dispatchers.IO) {
Settings.perAppProxyManagedList = chinaApps
}
isScanning = false
} }
} else { } else {
showRootDialog = false 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" const val PACKAGE_QUERY_MODE_ROOT = "ROOT"
var perAppProxyPackageQueryMode by dataStore.string(SettingsKey.PER_APP_PROXY_PACKAGE_QUERY_MODE) { PACKAGE_QUERY_MODE_SHIZUKU } 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) { fun getEffectivePerAppProxyList(): Set<String> = if (perAppProxyManagedMode) {
perAppProxyList union perAppProxyManagedList perAppProxyManagedList
} else { } else {
perAppProxyList perAppProxyList
} }