mirror of
https://github.com/wanghongenpin/proxypin.git
synced 2026-03-15 04:23:17 +08:00
Add option to include system apps in installed apps retrieval (#549)
This commit is contained in:
@@ -28,7 +28,16 @@ class InstalledAppsPlugin : AndroidFlutterPlugin() {
|
||||
"getInstalledApps" -> {
|
||||
val withIcon = call.argument<Boolean>("withIcon") ?: false
|
||||
val packageNamePrefix = call.argument<String>("packageNamePrefix") ?: ""
|
||||
result.success(getInstalledApps(withIcon, packageNamePrefix))
|
||||
val includeSystemApps = call.argument<Boolean>("includeSystemApps") ?: false
|
||||
Thread {
|
||||
result.success(
|
||||
getInstalledApps(
|
||||
withIcon,
|
||||
packageNamePrefix,
|
||||
includeSystemApps
|
||||
)
|
||||
)
|
||||
}.start()
|
||||
}
|
||||
|
||||
"getAppInfo" -> {
|
||||
@@ -48,27 +57,33 @@ class InstalledAppsPlugin : AndroidFlutterPlugin() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSystemApp(applicationInfo: ApplicationInfo?): Boolean {
|
||||
if (applicationInfo == null) return false
|
||||
return (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0
|
||||
}
|
||||
|
||||
private fun getInstalledApps(
|
||||
withIcon: Boolean,
|
||||
packageNamePrefix: String
|
||||
packageNamePrefix: String,
|
||||
includeSystemApps: Boolean
|
||||
): List<ProcessInfo> {
|
||||
val packageManager = activity.packageManager
|
||||
var installedApps = packageManager.getInstalledApplications(0)
|
||||
installedApps =
|
||||
installedApps.filter { app ->
|
||||
(app.flags and ApplicationInfo.FLAG_SYSTEM) <= 0
|
||||
|| (app.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
|
||||
|| packageManager.getLaunchIntentForPackage(app.packageName) != null
|
||||
}
|
||||
|
||||
if (packageNamePrefix.isNotEmpty())
|
||||
if (!includeSystemApps) {
|
||||
installedApps =
|
||||
installedApps.filter { app -> !isSystemApp(app) }
|
||||
}
|
||||
|
||||
if (packageNamePrefix.isNotEmpty()) {
|
||||
installedApps = installedApps.filter { app ->
|
||||
app.packageName.startsWith(
|
||||
packageNamePrefix.lowercase(Locale.ENGLISH)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val threadPoolExecutor = Executors.newFixedThreadPool(6)
|
||||
val threadPoolExecutor = Executors.newFixedThreadPool(4)
|
||||
installedApps.map { app ->
|
||||
val task: Callable<ProcessInfo> = Callable {
|
||||
ProcessInfo.create(packageManager, app, withIcon)
|
||||
@@ -84,4 +99,3 @@ class InstalledAppsPlugin : AndroidFlutterPlugin() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,16 @@ import 'package:flutter/services.dart';
|
||||
class InstalledApps {
|
||||
static const MethodChannel _methodChannel = MethodChannel('com.proxy/installedApps');
|
||||
|
||||
static Future<List<AppInfo>> getInstalledApps(bool withIcon, {String? packageNamePrefix}) {
|
||||
return _methodChannel
|
||||
.invokeListMethod<Map>('getInstalledApps', {"withIcon": withIcon, "packageNamePrefix": packageNamePrefix}).then(
|
||||
(value) => value?.map((e) => AppInfo.formJson(e)).toList() ?? []);
|
||||
static Future<List<AppInfo>> getInstalledApps(
|
||||
bool withIcon, {
|
||||
String? packageNamePrefix,
|
||||
bool includeSystemApps = false,
|
||||
}) {
|
||||
return _methodChannel.invokeListMethod<Map>('getInstalledApps', {
|
||||
"withIcon": withIcon,
|
||||
"packageNamePrefix": packageNamePrefix,
|
||||
"includeSystemApps": includeSystemApps,
|
||||
}).then((value) => value?.map((e) => AppInfo.formJson(e)).toList() ?? []);
|
||||
}
|
||||
|
||||
static Future<AppInfo> getAppInfo(String packageName) async {
|
||||
|
||||
@@ -29,7 +29,7 @@ class Configuration {
|
||||
int port = 9099;
|
||||
|
||||
//是否启用https抓包
|
||||
bool enableSsl = false;
|
||||
bool enableSsl = Platforms.isMobile();
|
||||
|
||||
//是否设置系统代理
|
||||
bool enableSystemProxy = true;
|
||||
|
||||
@@ -179,6 +179,8 @@ class _DesktopHomePagePageState extends State<DesktopHomePage> implements EventL
|
||||
'2. 增加请求解密,可配置AES自动解密消息体;\n'
|
||||
'3. HTTP Header 展示增加文本和表格切换;\n'
|
||||
'4. 增加 Request Param 列表展示;\n'
|
||||
'5. 应用过滤列表增加是否显示系统应用;\n'
|
||||
'6. 更新JSON深色主题色,以提高可见度和美观度;\n'
|
||||
'8. bug修复和改进;\n'
|
||||
: 'Note: HTTPS capture is disabled by default — please install the certificate before enabling HTTPS capture.\n\n'
|
||||
'1. Added WebSocket request testing in the Toolbox.\n'
|
||||
|
||||
@@ -16,12 +16,14 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:proxypin/l10n/app_localizations.dart';
|
||||
import 'package:proxypin/native/installed_apps.dart';
|
||||
import 'package:proxypin/native/vpn.dart';
|
||||
import 'package:proxypin/network/bin/configuration.dart';
|
||||
import 'package:proxypin/network/bin/server.dart';
|
||||
import 'package:proxypin/ui/component/widgets.dart';
|
||||
import 'package:proxypin/utils/task.dart';
|
||||
|
||||
///应用白名单 目前只支持安卓 ios没办法获取安装的列表
|
||||
///@author wang
|
||||
@@ -317,10 +319,41 @@ class InstalledAppsWidget extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _InstalledAppsWidgetState extends State<InstalledAppsWidget> {
|
||||
static Future<List<AppInfo>> apps = InstalledApps.getInstalledApps(true);
|
||||
static List<AppInfo>? apps;
|
||||
static bool includeSystemApps = false;
|
||||
|
||||
RxBool loading = false.obs;
|
||||
|
||||
String? keyword;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
DelayedTask().cancel("InstalledAppsWidget_release");
|
||||
if (apps != null) {
|
||||
return;
|
||||
}
|
||||
refreshApps();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
DelayedTask().debounce("InstalledAppsWidget_release", const Duration(seconds: 10), () {
|
||||
apps = null;
|
||||
includeSystemApps = false;
|
||||
});
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void refreshApps() async {
|
||||
try {
|
||||
loading.value = true;
|
||||
apps = await InstalledApps.getInstalledApps(true, includeSystemApps: includeSystemApps);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool isCN = Localizations.localeOf(context) == const Locale.fromSubtags(languageCode: 'zh');
|
||||
@@ -331,6 +364,18 @@ class _InstalledAppsWidgetState extends State<InstalledAppsWidget> {
|
||||
decoration: InputDecoration(
|
||||
hintText: isCN ? "请输入应用名或包名" : "Please enter the application or package name",
|
||||
border: InputBorder.none,
|
||||
hintStyle: TextStyle(color: Colors.grey.shade500),
|
||||
suffixIcon: IconButton(
|
||||
color: includeSystemApps ? Theme.of(context).colorScheme.primary : null,
|
||||
icon: const Icon(Icons.visibility_outlined),
|
||||
tooltip: isCN ? "显示系统应用" : "Show system apps",
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
includeSystemApps = !includeSystemApps;
|
||||
});
|
||||
refreshApps();
|
||||
},
|
||||
),
|
||||
),
|
||||
onChanged: (String value) {
|
||||
keyword = value.toLowerCase();
|
||||
@@ -340,45 +385,42 @@ class _InstalledAppsWidgetState extends State<InstalledAppsWidget> {
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
apps = InstalledApps.getInstalledApps(true);
|
||||
await apps;
|
||||
setState(() {});
|
||||
refreshApps();
|
||||
},
|
||||
child: FutureBuilder(
|
||||
future: apps,
|
||||
builder: (BuildContext context, AsyncSnapshot<List<AppInfo>> snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
List<AppInfo> appInfoList = snapshot.data!;
|
||||
appInfoList = appInfoList.toSet().difference(widget.addedList.toSet()).toList();
|
||||
if (keyword != null && keyword!.trim().isNotEmpty) {
|
||||
appInfoList = appInfoList
|
||||
.where((element) =>
|
||||
element.name!.toLowerCase().contains(keyword!) ||
|
||||
element.packageName!.toLowerCase().contains(keyword!))
|
||||
.toList();
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
itemCount: appInfoList.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
AppInfo appInfo = appInfoList[index];
|
||||
return ListTile(
|
||||
leading: Image.memory(appInfo.icon ?? Uint8List(0)),
|
||||
title: Text(appInfo.name ?? ""),
|
||||
subtitle: Text(appInfo.packageName ?? ""),
|
||||
onTap: () async {
|
||||
Navigator.of(context).pop(appInfo.packageName);
|
||||
},
|
||||
);
|
||||
});
|
||||
} else {
|
||||
return const Center(
|
||||
child: Obx(() => loading.value
|
||||
? const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
: buildAppListView()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ListView buildAppListView() {
|
||||
if (apps == null) {
|
||||
return ListView();
|
||||
}
|
||||
List<AppInfo> appInfoList = apps!;
|
||||
appInfoList = appInfoList.toSet().difference(widget.addedList.toSet()).toList();
|
||||
if (keyword != null && keyword!.trim().isNotEmpty) {
|
||||
appInfoList = appInfoList
|
||||
.where((element) =>
|
||||
element.name!.toLowerCase().contains(keyword!) || element.packageName!.toLowerCase().contains(keyword!))
|
||||
.toList();
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
itemCount: appInfoList.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
AppInfo appInfo = appInfoList[index];
|
||||
return ListTile(
|
||||
leading: Image.memory(appInfo.icon ?? Uint8List(0)),
|
||||
title: Text(appInfo.name ?? ""),
|
||||
subtitle: Text(appInfo.packageName ?? ""),
|
||||
onTap: () async {
|
||||
Navigator.of(context).pop(appInfo.packageName);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
51
lib/utils/task.dart
Normal file
51
lib/utils/task.dart
Normal file
@@ -0,0 +1,51 @@
|
||||
import 'dart:async';
|
||||
|
||||
/// 延时任务工具类
|
||||
class DelayedTask {
|
||||
// 私有构造函数,实现单例
|
||||
DelayedTask._internal();
|
||||
|
||||
static final DelayedTask _instance = DelayedTask._internal();
|
||||
|
||||
factory DelayedTask() => _instance;
|
||||
|
||||
// 维护一个任务池,支持同时管理多个不同的延时任务
|
||||
final Map<String, Timer> _taskPool = {};
|
||||
|
||||
/// 执行防抖任务 (Debounce)
|
||||
/// 如果在 [duration] 时间内再次调用相同 [tag] 的任务,前一个任务会被自动取消
|
||||
void debounce(
|
||||
String tag,
|
||||
Duration duration,
|
||||
void Function() action,
|
||||
) {
|
||||
// 1. 如果旧任务还在运行,直接取消
|
||||
_taskPool[tag]?.cancel();
|
||||
|
||||
// 2. 开启新任务
|
||||
_taskPool[tag] = Timer(duration, () {
|
||||
action();
|
||||
_taskPool.remove(tag); // 执行完毕后移除
|
||||
});
|
||||
}
|
||||
|
||||
/// 延迟 [duration] 后执行一次,返回可手动取消的 Timer
|
||||
/// 适用于不需要防抖,但需要精准手动控制取消的场景
|
||||
Timer delay(Duration duration, void Function() action) {
|
||||
return Timer(duration, action);
|
||||
}
|
||||
|
||||
/// 取消特定标签的任务
|
||||
void cancel(String tag) {
|
||||
if (_taskPool.containsKey(tag)) {
|
||||
_taskPool[tag]?.cancel();
|
||||
_taskPool.remove(tag);
|
||||
}
|
||||
}
|
||||
|
||||
/// 取消所有正在运行的任务 (通常在 dispose 时调用)
|
||||
void cancelAll() {
|
||||
_taskPool.forEach((tag, timer) => timer.cancel());
|
||||
_taskPool.clear();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user