Here, we use QUERY_ALL_PACKAGES permission to get installed applications.
class AppHostApi(
private val context: FragmentActivity,
) : BridgeHostApi {
override fun getInstalledApps(callback: (Result<List<AndroidAppInfo>>) -> Unit) {
scope.launch {
if (checkInstalledAppPermission()) {
val packageManager = context.packageManager
val installedApps = packageManager.getInstalledApplications(0)
val apps = mutableListOf<AndroidAppInfo>()
for (info in installedApps) {
val appInfo =
AndroidAppInfo(
packageManager.getApplicationLabel(info).toString(),
info.packageName
)
apps.add(appInfo)
}
callback(Result.success(apps))
} else {
callback(Result.success(listOf()))
}
}
}
private suspend fun checkInstalledAppPermission(): Boolean {
// android 11, level 30
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val permissions = mutableListOf<String>()
permissions.add(Manifest.permission.QUERY_ALL_PACKAGES)
val request = context.permissionsBuilder(permissions).build()
val result = request.sendSuspend()
val granted = result.allGranted()
return granted
} else {
return true
}
}
}Here, we show installed applications to users, and let them choose which apps they want to use vpn.
class SelectedAppState extends GetxController {
final apps = <AndroidAppInfo>[].obs;
final _allApps = <AndroidAppInfo>[];
final _selections = <String>{};
Future<void> _queryApps() async {
final androidApps = await AppHostApi().getInstalledApps();
_allApps.clear();
_allApps.addAll(androidApps);
_refreshApps();
}
void _refreshApps() {
final selections = <String>{};
final selectedApps = <AndroidAppInfo>[];
for (final app in _allApps) {
for (final selection in _selections) {
if (app.packageName == selection) {
selections.add(selection);
selectedApps.add(app);
}
}
}
_selections.clear();
_selections.addAll(selections);
apps.clear();
apps.addAll(selectedApps);
}
}Here, we pass the packages to vpn builder.
class TProxyService : VpnService() {
private fun runTun(
request: StartVpnRequest
) {
if (tunnel != null) {
return
}
XLog.d("tunnel = null")
val builder = Builder()
// Loop through the app packages in the array and confirm that the app is
// installed before adding the app to the allowed list.
request.perAppVPN?.let {
if (it.isNotEmpty()) {
for (appPackage in it) {
try {
packageManager.getPackageInfo(appPackage, 0)
builder.addAllowedApplication(appPackage)
} catch (_: PackageManager.NameNotFoundException) {
}
}
}
}
builder.addAddress(ipv4Address, 32)
.addRoute("0.0.0.0", 0)
.addAddress(ipv6Address, 128)
.addRoute("::", 0)
.setMtu(tunMtu)
request.tunDnsIPv4?.let {
builder.addDnsServer(it)
}
request.tunDnsIPv6?.let {
builder.addDnsServer(it)
}
tunnel = builder.establish()
XLog.d("tunnel = ${tunnel?.fd}")
tunnel?.fd?.let { fd ->
controller.vpn = this
runXray(request)
request.tunFilePath?.let { tunFilePath ->
runTun2socks(tunFilePath, fd)
XLog.d("tun $tunFilePath")
}
running = true
updateDataStore(true)
}
}
}