feature/app-split-tunneling (#702)

App Split Tunneling for Windows and Android
This commit is contained in:
Nethius 2024-04-01 18:45:00 +07:00 committed by GitHub
parent e7bd24f065
commit adab30fc81
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 1225 additions and 98 deletions

View file

@ -7,6 +7,7 @@ import android.content.Intent.EXTRA_MIME_TYPES
import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
import android.content.ServiceConnection
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.net.Uri
import android.net.VpnService
import android.os.Bundle
@ -24,12 +25,15 @@ import androidx.core.content.ContextCompat
import java.io.IOException
import kotlin.LazyThreadSafetyMode.NONE
import kotlin.text.RegexOption.IGNORE_CASE
import AppListProvider
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.amnezia.vpn.protocol.getStatistics
import org.amnezia.vpn.protocol.getStatus
import org.amnezia.vpn.qt.QtAndroidController
@ -484,4 +488,24 @@ class AmneziaActivity : QtActivity() {
moveTaskToBack(false)
}
}
@Suppress("unused")
fun getAppList(): String {
Log.v(TAG, "Get app list")
var appList = ""
runBlocking {
mainScope.launch {
withContext(Dispatchers.IO) {
appList = AppListProvider.getAppList(packageManager, packageName)
}
}.join()
}
return appList
}
@Suppress("unused")
fun getAppIcon(packageName: String, width: Int, height: Int): Bitmap {
Log.v(TAG, "Get app icon: $packageName")
return AppListProvider.getAppIcon(packageManager, packageName, width, height)
}
}

View file

@ -0,0 +1,73 @@
import android.Manifest.permission.INTERNET
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.NameNotFoundException
import android.graphics.Bitmap
import android.graphics.Bitmap.Config.ARGB_8888
import androidx.core.graphics.drawable.toBitmapOrNull
import org.amnezia.vpn.util.Log
import org.json.JSONArray
import org.json.JSONObject
private const val TAG = "AppListProvider"
object AppListProvider {
fun getAppList(pm: PackageManager, selfPackageName: String): String {
val jsonArray = JSONArray()
pm.getPackagesHoldingPermissions(arrayOf(INTERNET), 0)
.filter { it.packageName != selfPackageName }
.map { App(it, pm) }
.sortedWith(App::compareTo)
.map(App::toJson)
.forEach(jsonArray::put)
return jsonArray.toString()
}
fun getAppIcon(pm: PackageManager, packageName: String, width: Int, height: Int): Bitmap {
val icon = try {
pm.getApplicationIcon(packageName)
} catch (e: NameNotFoundException) {
Log.e(TAG, "Package $packageName was not found: $e")
pm.defaultActivityIcon
}
val w: Int = if (width > 0) width else icon.intrinsicWidth
val h: Int = if (height > 0) height else icon.intrinsicHeight
return icon.toBitmapOrNull(w, h, ARGB_8888)
?: Bitmap.createBitmap(w, h, ARGB_8888)
}
}
private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo = pi.applicationInfo) : Comparable<App> {
val name: String?
val packageName: String = pi.packageName
val icon: Boolean = ai.icon != 0
val isLaunchable: Boolean = pm.getLaunchIntentForPackage(packageName) != null
init {
val name = ai.loadLabel(pm).toString()
this.name = if (name != packageName) name else null
}
override fun compareTo(other: App): Int {
val r = other.isLaunchable.compareTo(isLaunchable)
if (r != 0) return r
if (name != other.name) {
return when {
name == null -> 1
other.name == null -> -1
else -> String.CASE_INSENSITIVE_ORDER.compare(name, other.name)
}
}
return String.CASE_INSENSITIVE_ORDER.compare(packageName, other.packageName)
}
fun toJson(): JSONObject {
val jsonObject = JSONObject()
jsonObject.put("package", packageName)
jsonObject.put("name", name)
jsonObject.put("icon", icon)
jsonObject.put("launchable", isLaunchable)
return jsonObject
}
}