app blacklist

This commit is contained in:
wanghongenpin
2024-03-31 17:34:24 +08:00
parent 652b66b58f
commit 7452a8f7e2
15 changed files with 210 additions and 30 deletions

View File

@@ -31,6 +31,7 @@ class ProxyVpnService : VpnService(), ProtectSocket {
const val PROXY_HOST_KEY = "ProxyHost"
const val PROXY_PORT_KEY = "ProxyPort"
const val ALLOW_APPS_KEY = "AllowApps" //允许的名单
const val DISALLOW_APPS_KEY = "DisallowApps" //禁止的名单
/**
* 动作:断开连接
@@ -48,6 +49,7 @@ class ProxyVpnService : VpnService(), ProtectSocket {
var host: String? = null
var port: Int = 0
var allowApps: ArrayList<String>? = null
private var disallowApps: ArrayList<String>? = null
fun stopVpnIntent(context: Context): Intent {
return Intent(context, ProxyVpnService::class.java).also {
@@ -59,12 +61,14 @@ class ProxyVpnService : VpnService(), ProtectSocket {
context: Context,
proxyHost: String? = host,
proxyPort: Int? = port,
allowApps: ArrayList<String>? = this.allowApps
allowApps: ArrayList<String>? = this.allowApps,
disallowApps: ArrayList<String>? = this.disallowApps
): Intent {
return Intent(context, ProxyVpnService::class.java).also {
it.putExtra(PROXY_HOST_KEY, proxyHost)
it.putExtra(PROXY_PORT_KEY, proxyPort)
it.putStringArrayListExtra(ALLOW_APPS_KEY, allowApps)
it.putStringArrayListExtra(DISALLOW_APPS_KEY, disallowApps)
}
}
}
@@ -82,7 +86,8 @@ class ProxyVpnService : VpnService(), ProtectSocket {
connect(
intent.getStringExtra(PROXY_HOST_KEY) ?: host!!,
intent.getIntExtra(PROXY_PORT_KEY, port),
intent.getStringArrayListExtra(ALLOW_APPS_KEY) ?: allowApps
intent.getStringArrayListExtra(ALLOW_APPS_KEY) ?: allowApps,
intent.getStringArrayListExtra(DISALLOW_APPS_KEY)
)
START_STICKY
}
@@ -98,13 +103,19 @@ class ProxyVpnService : VpnService(), ProtectSocket {
isRunning = false
}
private fun connect(proxyHost: String, proxyPort: Int, allowPackages: ArrayList<String>?) {
private fun connect(
proxyHost: String,
proxyPort: Int,
allowPackages: ArrayList<String>?,
disallowPackages: ArrayList<String>?
) {
Log.i("ProxyVpnService", "startVpn $host:$port $allowApps")
host = proxyHost
port = proxyPort
allowApps = allowPackages
vpnInterface = createVpnInterface(proxyHost, proxyPort, allowPackages)
disallowApps = disallowPackages
vpnInterface = createVpnInterface(proxyHost, proxyPort, allowPackages, disallowPackages)
if (vpnInterface == null) {
val alertDialog = Intent(applicationContext, VpnAlertDialog::class.java)
.setAction("com.network.proxy.ProxyVpnService")
@@ -152,7 +163,12 @@ class ProxyVpnService : VpnService(), ProtectSocket {
}
private fun createVpnInterface(proxyHost: String, proxyPort: Int, allowPackages: List<String>?):
private fun createVpnInterface(
proxyHost: String,
proxyPort: Int,
allowPackages: List<String>?,
disallowApps: ArrayList<String>?
):
ParcelFileDescriptor? {
val build = Builder()
.setMtu(MAX_PACKET_LEN)
@@ -170,6 +186,10 @@ class ProxyVpnService : VpnService(), ProtectSocket {
build.addDisallowedApplication(baseContext.packageName)
}
disallowApps?.forEach {
build.addDisallowedApplication(it)
}
build.setConfigureIntent(
PendingIntent.getActivity(
this,

View File

@@ -24,6 +24,8 @@ class PictureInPicturePlugin : AndroidFlutterPlugin() {
var channel: MethodChannel? = null
var proxyHost: String? = null
var proxyPort: Int? = null
var allowApps: ArrayList<String>? = null
var disallowApps: ArrayList<String>? = null
///广播事件接受者
private val vpnBroadcastReceiver = object : BroadcastReceiver() {
@@ -43,7 +45,15 @@ class PictureInPicturePlugin : AndroidFlutterPlugin() {
if (isRunning) {
activity.startService(ProxyVpnService.stopVpnIntent(activity))
} else {
activity.startService(ProxyVpnService.startVpnIntent(activity, proxyHost, proxyPort))
activity.startService(
ProxyVpnService.startVpnIntent(
activity,
proxyHost,
proxyPort,
allowApps,
disallowApps
)
)
}
//设置画中画参数
@@ -67,6 +77,8 @@ class PictureInPicturePlugin : AndroidFlutterPlugin() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
proxyHost = call.argument<String>("proxyHost")
proxyPort = call.argument<Int>("proxyPort")
allowApps = call.argument<ArrayList<String>>("allowApps")
disallowApps = call.argument<ArrayList<String>>("disallowApps")
val param = updatePictureInPictureParams(ProxyVpnService.isRunning)
if (!registerBroadcast) {

View File

@@ -23,9 +23,10 @@ class VpnServicePlugin : AndroidFlutterPlugin() {
val host = call.argument<String>("proxyHost")
val port = call.argument<Int>("proxyPort")
val allowApps = call.argument<ArrayList<String>>("allowApps")
val disallowApps = call.argument<ArrayList<String>>("disallowApps")
val prepareVpn = prepareVpn(host!!, port!!, allowApps)
if (prepareVpn) {
startVpn(host, port, allowApps)
startVpn(host, port, allowApps, disallowApps)
}
result.success(prepareVpn)
}
@@ -39,8 +40,9 @@ class VpnServicePlugin : AndroidFlutterPlugin() {
val host = call.argument<String>("proxyHost")
val port = call.argument<Int>("proxyPort")
val allowApps = call.argument<ArrayList<String>>("allowApps")
val disallowApps = call.argument<ArrayList<String>>("disallowApps")
stopVpn()
startVpn(host!!, port!!, allowApps)
startVpn(host!!, port!!, allowApps, disallowApps)
}
else -> {
@@ -69,8 +71,13 @@ class VpnServicePlugin : AndroidFlutterPlugin() {
/**
* 启动vpn服务
*/
private fun startVpn(host: String, port: Int, allowApps: ArrayList<String>?) {
val intent = ProxyVpnService.startVpnIntent(activity, host, port, allowApps)
private fun startVpn(
host: String,
port: Int,
allowApps: ArrayList<String>?,
disallowApps: ArrayList<String>?
) {
val intent = ProxyVpnService.startVpnIntent(activity, host, port, allowApps, disallowApps)
activity.startService(intent)
}

View File

@@ -23,7 +23,7 @@ class ProcessInfoManager private constructor() {
}
private val localPortUidMap =
CacheBuilder.newBuilder().maximumSize(10_000).expireAfterAccess(120, TimeUnit.SECONDS)
CacheBuilder.newBuilder().maximumSize(10_000).expireAfterAccess(60, TimeUnit.SECONDS)
.build<Int, Int>()
private val appInfoCache =

View File

@@ -110,6 +110,7 @@
"domainBlacklist": "Domain Blacklist",
"domainFilter": "Domain Filter",
"appWhitelist": "App Whitelist",
"appBlacklist": "App Blacklist",
"scanCode": "Scan Code Connect",
"addBlacklist": "Add Filter Blacklist",
"addWhitelist": "Add Filter Whitelist",

View File

@@ -109,6 +109,7 @@
"domainWhitelist": "域名白名单",
"domainBlacklist" :"域名黑名单",
"appWhitelist": "应用白名单",
"appBlacklist": "应用黑名单",
"domainFilter": "域名过滤",
"scanCode": "扫码连接",
"addBlacklist": "添加黑名单",

View File

@@ -28,9 +28,10 @@ class PictureInPicture {
});
///进入画中画模式
static Future<bool> enterPictureInPictureMode(String host, int port) async {
final bool enterPictureInPictureMode =
await _channel.invokeMethod('enterPictureInPictureMode', {"proxyHost": host, "proxyPort": port});
static Future<bool> enterPictureInPictureMode(String host, int port,
{List<String>? appList, List<String>? disallowApps}) async {
final bool enterPictureInPictureMode = await _channel.invokeMethod('enterPictureInPictureMode',
{"proxyHost": host, "proxyPort": port, "allowApps": appList, "disallowApps": disallowApps});
inPip = true;
return enterPictureInPictureMode;

View File

@@ -5,9 +5,9 @@ class Vpn {
static bool isVpnStarted = false; //vpn是否已经启动
static startVpn(String host, int port, {List<String>? appList, bool? backgroundAudioEnable = true}) {
proxyVpnChannel.invokeMethod("startVpn",
{"proxyHost": host, "proxyPort": port, "allowApps": appList, "backgroundAudioEnable": backgroundAudioEnable});
static startVpn(String host, int port, {List<String>? appList, List<String>? disallowApps}) {
proxyVpnChannel.invokeMethod(
"startVpn", {"proxyHost": host, "proxyPort": port, "allowApps": appList, "disallowApps": disallowApps});
isVpnStarted = true;
}
@@ -17,9 +17,9 @@ class Vpn {
}
//重启vpn
static restartVpn(String host, int port, {List<String>? appList, bool? backgroundAudioEnable = true}) {
proxyVpnChannel.invokeMethod("restartVpn",
{"proxyHost": host, "proxyPort": port, "allowApps": appList, "backgroundAudioEnable": backgroundAudioEnable});
static restartVpn(String host, int port, {List<String>? appList, List<String>? disallowApps}) {
proxyVpnChannel.invokeMethod(
"restartVpn", {"proxyHost": host, "proxyPort": port, "allowApps": appList, "disallowApps": disallowApps});
isVpnStarted = true;
}

View File

@@ -43,6 +43,9 @@ class Configuration {
//白名单应用
List<String> appWhitelist = [];
//应用黑名单
List<String>? appBlacklist;
//远程连接 不持久化保存
String? remoteHost;
@@ -84,6 +87,7 @@ class Configuration {
externalProxy = ProxyInfo.fromJson(config['externalProxy']);
}
appWhitelist = List<String>.from(config['appWhitelist'] ?? []);
appBlacklist = config['appBlacklist'] == null ? null : List<String>.from(config['appBlacklist']);
HostFilter.whitelist.load(config['whitelist']);
HostFilter.blacklist.load(config['blacklist']);
}
@@ -131,6 +135,7 @@ class Configuration {
'proxyPassDomains': proxyPassDomains,
'externalProxy': externalProxy?.toJson(),
'appWhitelist': appWhitelist,
'appBlacklist': appBlacklist,
'historyCacheTime': historyCacheTime,
'whitelist': HostFilter.whitelist.toJson(),
'blacklist': HostFilter.blacklist.toJson(),

View File

@@ -15,6 +15,15 @@ class ExpiringCache<K, V> {
_expirationTimes[key] = Timer(duration, () => remove(key));
}
V? putIfAbsent(K key, V Function() ifAbsent) {
if (_cache.containsKey(key)) {
return _cache[key];
}
final value = ifAbsent();
set(key, value);
return value;
}
V? get(K key) {
return _cache[key];
}

View File

@@ -14,7 +14,7 @@ import 'package:network_proxy/ui/configuration.dart';
import 'package:network_proxy/ui/mobile/menu/preference.dart';
import 'package:network_proxy/ui/mobile/request/favorite.dart';
import 'package:network_proxy/ui/mobile/request/history.dart';
import 'package:network_proxy/ui/mobile/setting/app_whitelist.dart';
import 'package:network_proxy/ui/mobile/setting/app_filter.dart';
import 'package:network_proxy/ui/mobile/setting/filter.dart';
import 'package:network_proxy/ui/mobile/setting/request_block.dart';
import 'package:network_proxy/ui/mobile/setting/request_rewrite.dart';
@@ -153,6 +153,12 @@ class FilterMenu extends StatelessWidget {
title: Text(localizations.appWhitelist),
trailing: const Icon(Icons.arrow_right),
onTap: () => navigator(context, AppWhitelist(proxyServer: proxyServer))),
Platform.isIOS
? const SizedBox()
: ListTile(
title: Text(localizations.appBlacklist),
trailing: const Icon(Icons.arrow_right),
onTap: () => navigator(context, AppBlacklist(proxyServer: proxyServer))),
])));
}
}

View File

@@ -66,7 +66,8 @@ class MobileHomeState extends State<MobileHomePage> implements EventListener, Li
}
return PictureInPicture.enterPictureInPictureMode(
Platform.isAndroid ? await localIp() : "127.0.0.1", proxyServer.port);
Platform.isAndroid ? await localIp() : "127.0.0.1", proxyServer.port,
appList: proxyServer.configuration.appWhitelist, disallowApps: proxyServer.configuration.appBlacklist);
}
return false;
}
@@ -207,8 +208,8 @@ class MobileHomeState extends State<MobileHomePage> implements EventListener, Li
serverLaunch: false,
onStart: () async {
Vpn.startVpn(Platform.isAndroid ? await localIp() : "127.0.0.1", proxyServer.port,
backgroundAudioEnable: widget.appConfiguration.iosVpnBackgroundAudioEnable,
appList: proxyServer.configuration.appWhitelist);
appList: proxyServer.configuration.appWhitelist,
disallowApps: proxyServer.configuration.appBlacklist);
},
onStop: () => Vpn.stopVpn())),
);

View File

@@ -9,6 +9,7 @@ import 'package:network_proxy/network/bin/server.dart';
import 'package:network_proxy/network/host_port.dart';
import 'package:network_proxy/network/http/http.dart';
import 'package:network_proxy/network/http_client.dart';
import 'package:network_proxy/network/util/cache.dart';
import 'package:network_proxy/storage/favorites.dart';
import 'package:network_proxy/ui/component/utils.dart';
import 'package:network_proxy/ui/content/panel.dart';
@@ -42,6 +43,8 @@ class RequestRow extends StatefulWidget {
}
class RequestRowState extends State<RequestRow> {
static ExpiringCache<String, Image> imageCache = ExpiringCache<String, Image>(const Duration(minutes: 5));
late HttpRequest request;
HttpResponse? response;
bool selected = false;
@@ -114,8 +117,10 @@ class RequestRowState extends State<RequestRow> {
}
//如果有缓存图标直接返回图标
if(request.processInfo!.hasCacheIcon){
return Image.memory(request.processInfo!.cacheIcon!, width: 40);
if (request.processInfo!.hasCacheIcon) {
return imageCache.putIfAbsent(request.processInfo!.id, () {
return Image.memory(request.processInfo!.cacheIcon!, width: 40, gaplessPlayback: true);
});
}
return FutureBuilder(

View File

@@ -6,7 +6,6 @@ import 'package:network_proxy/native/installed_apps.dart';
import 'package:network_proxy/native/vpn.dart';
import 'package:network_proxy/network/bin/configuration.dart';
import 'package:network_proxy/network/bin/server.dart';
import 'package:network_proxy/ui/configuration.dart';
// ios没办法获取安装的列表
class AppWhitelist extends StatefulWidget {
@@ -37,8 +36,7 @@ class _AppWhitelistState extends State<AppWhitelist> {
configuration.flushConfig();
if (Vpn.isVpnStarted) {
Vpn.restartVpn("127.0.0.1", widget.proxyServer.port,
appList: configuration.appWhitelist,
backgroundAudioEnable: AppConfiguration.current?.iosVpnBackgroundAudioEnable);
appList: configuration.appWhitelist, disallowApps: configuration.appBlacklist);
}
}
super.dispose();
@@ -126,6 +124,118 @@ class _AppWhitelistState extends State<AppWhitelist> {
}
}
class AppBlacklist extends StatefulWidget {
final ProxyServer proxyServer;
const AppBlacklist({super.key, required this.proxyServer});
@override
State<AppBlacklist> createState() => _AppBlacklistState();
}
class _AppBlacklistState extends State<AppBlacklist> {
late Configuration configuration;
bool changed = false;
AppLocalizations get localizations => AppLocalizations.of(context)!;
@override
void initState() {
super.initState();
configuration = widget.proxyServer.configuration;
}
@override
void dispose() {
if (changed) {
configuration.flushConfig();
if (Vpn.isVpnStarted) {
Vpn.restartVpn("127.0.0.1", widget.proxyServer.port,
appList: configuration.appWhitelist, disallowApps: configuration.appBlacklist);
}
}
super.dispose();
}
@override
Widget build(BuildContext context) {
bool isCN = Localizations.localeOf(context) == const Locale.fromSubtags(languageCode: 'zh');
var appBlacklist = <Future<AppInfo>>[];
for (var element in configuration.appBlacklist ?? []) {
appBlacklist.add(InstalledApps.getAppInfo(element).catchError((e) {
return AppInfo(name: isCN ? "未知应用" : "Unknown app", packageName: element);
}));
}
return Scaffold(
appBar: AppBar(
title: Text(localizations.appBlacklist, style: const TextStyle(fontSize: 16)),
actions: [
IconButton(
icon: const Icon(Icons.add),
onPressed: () {
//
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => const InstalledAppsWidget()))
.then((value) {
if (value != null) {
if (configuration.appBlacklist?.contains(value) == true) {
return;
}
setState(() {
configuration.appBlacklist ??= [];
configuration.appBlacklist?.add(value);
changed = true;
});
}
});
},
),
],
),
body: FutureBuilder(
future: Future.wait(appBlacklist),
builder: (BuildContext context, AsyncSnapshot<List<AppInfo>> snapshot) {
if (snapshot.hasData) {
if (snapshot.data!.isEmpty) {
return Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: Text(localizations.emptyData, style: const TextStyle(color: Colors.grey))),
);
}
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (BuildContext context, int index) {
AppInfo appInfo = snapshot.data![index];
return ListTile(
leading: appInfo.icon == null ? const Icon(Icons.question_mark) : Image.memory(appInfo.icon!),
title: Text(appInfo.name ?? ""),
subtitle: Text(appInfo.packageName ?? ""),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
//
setState(() {
configuration.appBlacklist?.remove(appInfo.packageName);
changed = true;
});
},
),
);
});
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
}),
);
}
}
///app列表
class InstalledAppsWidget extends StatefulWidget {
const InstalledAppsWidget({super.key});

View File

@@ -130,7 +130,9 @@ class _PictureInPictureState extends State<PictureInPictureIcon> {
tooltip: localizations.windowMode,
onPressed: () async {
PictureInPicture.enterPictureInPictureMode(
Platform.isAndroid ? await localIp() : "127.0.0.1", widget.proxyServer.port);
Platform.isAndroid ? await localIp() : "127.0.0.1", widget.proxyServer.port,
appList: widget.proxyServer.configuration.appWhitelist,
disallowApps: widget.proxyServer.configuration.appBlacklist);
},
icon: const Icon(Icons.picture_in_picture_alt))),
)