Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c78e9fcbd | |||
| 8bdeff515e | |||
| fa89715ede | |||
| 4a94905893 | |||
| cf184f2042 | |||
| e0ddb8730d | |||
| c8c041b872 |
@@ -8,6 +8,7 @@ plugins {
|
||||
alias(libs.plugins.compose.compiler)
|
||||
alias(libs.plugins.grgit)
|
||||
alias(libs.plugins.licensee)
|
||||
alias(libs.plugins.aboutlibraries)
|
||||
}
|
||||
|
||||
ksp {
|
||||
@@ -17,9 +18,15 @@ ksp {
|
||||
licensee {
|
||||
allowedLicenses().forEach { allow(it) }
|
||||
allowedLicenseUrls().forEach { allowUrl(it) }
|
||||
// foss, but missing licenses
|
||||
ignoreDependencies("com.github.T8RIN.QuickieExtended")
|
||||
ignoreDependencies("com.github.topjohnwu.libsu")
|
||||
allowDependency("com.github.T8RIN.QuickieExtended", "quickie-foss", "1.18.1") {
|
||||
because("FOSS library, but JitPack doesn't publish license metadata")
|
||||
allow("Apache-2.0")
|
||||
}
|
||||
|
||||
allowDependency("com.github.topjohnwu.libsu", "core", "6.0.0") {
|
||||
because("FOSS library, but JitPack doesn't publish license metadata")
|
||||
allow("Apache-2.0")
|
||||
}
|
||||
}
|
||||
|
||||
configure<ApplicationExtension> {
|
||||
@@ -94,21 +101,24 @@ configure<ApplicationExtension> {
|
||||
"proguard-rules.pro",
|
||||
)
|
||||
signingConfig = signingConfigs.getByName(Constants.RELEASE)
|
||||
resValue("string", "provider", "\"${Constants.APP_NAME}.provider\"")
|
||||
manifestPlaceholders["providerAuthority"] = "${Constants.APP_NAME}.provider"
|
||||
buildConfigField("String", "FILE_PROVIDER_AUTHORITY", "\"${Constants.APP_NAME}.provider\"")
|
||||
}
|
||||
|
||||
debug {
|
||||
applicationIdSuffix = ".debug"
|
||||
resValue("string", "app_name", "WG Tunnel Debug")
|
||||
isDebuggable = true
|
||||
resValue("string", "provider", "\"${Constants.APP_NAME}.provider.debug\"")
|
||||
manifestPlaceholders["providerAuthority"] = "${Constants.APP_NAME}.provider.debug"
|
||||
buildConfigField("String", "FILE_PROVIDER_AUTHORITY", "\"${Constants.APP_NAME}.provider.debug\"")
|
||||
}
|
||||
|
||||
create(Constants.NIGHTLY) {
|
||||
initWith(buildTypes.getByName(Constants.RELEASE))
|
||||
applicationIdSuffix = ".nightly"
|
||||
resValue("string", "app_name", "WG Tunnel Nightly")
|
||||
resValue("string", "provider", "\"${Constants.APP_NAME}.provider.nightly\"")
|
||||
manifestPlaceholders["providerAuthority"] = "${Constants.APP_NAME}.provider.nightly"
|
||||
buildConfigField("String", "FILE_PROVIDER_AUTHORITY", "\"${Constants.APP_NAME}.provider.nightly\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,6 +241,7 @@ dependencies {
|
||||
implementation(libs.bundles.ui.utilities)
|
||||
implementation(libs.lottie.compose)
|
||||
implementation(libs.sonner)
|
||||
implementation(libs.aboutlibraries.compose)
|
||||
|
||||
// Misc utilities
|
||||
implementation(libs.bundles.misc.utilities)
|
||||
@@ -271,17 +282,6 @@ dependencies {
|
||||
implementation(libs.koin.worker)
|
||||
}
|
||||
|
||||
tasks.register<Copy>("copyLicenseeJsonToAssets") {
|
||||
dependsOn("licensee")
|
||||
val outputAssets = layout.projectDirectory.dir("src/main/assets")
|
||||
from(layout.buildDirectory.file("reports/licensee/androidFdroidRelease/artifacts.json")) {
|
||||
rename("artifacts.json", "licenses.json")
|
||||
}
|
||||
into(outputAssets)
|
||||
}
|
||||
|
||||
tasks.named("preBuild") { dependsOn("copyLicenseeJsonToAssets") }
|
||||
|
||||
// https://gist.github.com/obfusk/61046e09cee352ae6dd109911534b12e#fix-proposed-by-linsui-disable-baseline-profiles
|
||||
tasks.configureEach {
|
||||
if (name.contains("ArtProfile")) {
|
||||
|
||||
@@ -0,0 +1,520 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 32,
|
||||
"identityHash": "fd4803fc483f41704303be9246dcfb4d",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "tunnel_config",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `tunnel_networks` TEXT NOT NULL DEFAULT '', `is_mobile_data_tunnel` INTEGER NOT NULL DEFAULT false, `is_primary_tunnel` INTEGER NOT NULL DEFAULT false, `quick_config` TEXT NOT NULL DEFAULT '', `dynamic_dns` INTEGER NOT NULL DEFAULT false, `is_ethernet_tunnel` INTEGER NOT NULL DEFAULT false, `prefer_ipv6` INTEGER NOT NULL DEFAULT false, `position` INTEGER NOT NULL DEFAULT 0, `auto_tunnel_apps` TEXT NOT NULL DEFAULT '[]', `is_metered` INTEGER NOT NULL DEFAULT false, `ipv4_fallback` INTEGER NOT NULL DEFAULT false, `ipv6_restore` INTEGER NOT NULL DEFAULT false)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "tunnelNetworks",
|
||||
"columnName": "tunnel_networks",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isMobileDataTunnel",
|
||||
"columnName": "is_mobile_data_tunnel",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isPrimaryTunnel",
|
||||
"columnName": "is_primary_tunnel",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "quickConfig",
|
||||
"columnName": "quick_config",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "dynamicDnsEnabled",
|
||||
"columnName": "dynamic_dns",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isEthernetTunnel",
|
||||
"columnName": "is_ethernet_tunnel",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isIpv6Preferred",
|
||||
"columnName": "prefer_ipv6",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "position",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "autoTunnelApps",
|
||||
"columnName": "auto_tunnel_apps",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "'[]'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isMetered",
|
||||
"columnName": "is_metered",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "ipv4FallbackEnabled",
|
||||
"columnName": "ipv4_fallback",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "ipv6RestoreEnabled",
|
||||
"columnName": "ipv6_restore",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_tunnel_config_name",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"name"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_tunnel_config_name` ON `${TABLE_NAME}` (`name`)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "proxy_settings",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `socks5_proxy_enabled` INTEGER NOT NULL DEFAULT 0, `socks5_proxy_bind_address` TEXT, `http_proxy_enable` INTEGER NOT NULL DEFAULT 0, `http_proxy_bind_address` TEXT, `proxy_username` TEXT, `proxy_password` TEXT)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "socks5ProxyEnabled",
|
||||
"columnName": "socks5_proxy_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "socks5ProxyBindAddress",
|
||||
"columnName": "socks5_proxy_bind_address",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "httpProxyEnabled",
|
||||
"columnName": "http_proxy_enable",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "httpProxyBindAddress",
|
||||
"columnName": "http_proxy_bind_address",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "proxyUsername",
|
||||
"columnName": "proxy_username",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "proxyPassword",
|
||||
"columnName": "proxy_password",
|
||||
"affinity": "TEXT"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "general_settings",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_shortcuts_enabled` INTEGER NOT NULL DEFAULT 0, `is_restore_on_boot_enabled` INTEGER NOT NULL DEFAULT 0, `is_multi_tunnel_enabled` INTEGER NOT NULL DEFAULT 0, `global_split_tunnel_enabled` INTEGER NOT NULL DEFAULT 0, `app_mode` INTEGER NOT NULL DEFAULT 0, `theme` TEXT NOT NULL DEFAULT 'AUTOMATIC', `locale` TEXT, `remote_key` TEXT, `is_remote_control_enabled` INTEGER NOT NULL DEFAULT 0, `is_pin_lock_enabled` INTEGER NOT NULL DEFAULT 0, `is_always_on_vpn_enabled` INTEGER NOT NULL DEFAULT 0, `already_donated` INTEGER NOT NULL DEFAULT 0, `screen_recording_security` INTEGER NOT NULL DEFAULT 1, `global_amnezia_enabled` INTEGER NOT NULL DEFAULT 0, `tunnel_scripting_enabled` INTEGER NOT NULL DEFAULT 0, `seamless_roaming_enabled` INTEGER NOT NULL DEFAULT 0)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isShortcutsEnabled",
|
||||
"columnName": "is_shortcuts_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isRestoreOnBootEnabled",
|
||||
"columnName": "is_restore_on_boot_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isMultiTunnelEnabled",
|
||||
"columnName": "is_multi_tunnel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isGlobalSplitTunnelEnabled",
|
||||
"columnName": "global_split_tunnel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "tunnelMode",
|
||||
"columnName": "app_mode",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "theme",
|
||||
"columnName": "theme",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "'AUTOMATIC'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "locale",
|
||||
"columnName": "locale",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "remoteKey",
|
||||
"columnName": "remote_key",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isRemoteControlEnabled",
|
||||
"columnName": "is_remote_control_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isPinLockEnabled",
|
||||
"columnName": "is_pin_lock_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAlwaysOnVpnEnabled",
|
||||
"columnName": "is_always_on_vpn_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "alreadyDonated",
|
||||
"columnName": "already_donated",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "screenRecordingSecurityEnabled",
|
||||
"columnName": "screen_recording_security",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "1"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isGlobalAmneziaEnabled",
|
||||
"columnName": "global_amnezia_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "tunnelScriptingEnabled",
|
||||
"columnName": "tunnel_scripting_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "seamlessRoamingEnabled",
|
||||
"columnName": "seamless_roaming_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "auto_tunnel_settings",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL DEFAULT 0, `is_tunnel_on_mobile_data_enabled` INTEGER NOT NULL DEFAULT 0, `trusted_network_ssids` TEXT NOT NULL DEFAULT '', `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL DEFAULT 0, `is_tunnel_on_wifi_enabled` INTEGER NOT NULL DEFAULT 0, `is_wildcards_enabled` INTEGER NOT NULL DEFAULT 0, `is_stop_on_no_internet_enabled` INTEGER NOT NULL DEFAULT 0, `is_tunnel_on_unsecure_enabled` INTEGER NOT NULL DEFAULT 0, `wifi_detection_method` INTEGER NOT NULL DEFAULT 0, `start_on_boot` INTEGER NOT NULL DEFAULT 0, `disable_on_captive_portal` INTEGER NOT NULL DEFAULT 1)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAutoTunnelEnabled",
|
||||
"columnName": "is_tunnel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnMobileDataEnabled",
|
||||
"columnName": "is_tunnel_on_mobile_data_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "trustedNetworkSSIDs",
|
||||
"columnName": "trusted_network_ssids",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnEthernetEnabled",
|
||||
"columnName": "is_tunnel_on_ethernet_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnWifiEnabled",
|
||||
"columnName": "is_tunnel_on_wifi_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isWildcardsEnabled",
|
||||
"columnName": "is_wildcards_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isStopOnNoInternetEnabled",
|
||||
"columnName": "is_stop_on_no_internet_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnUnsecureEnabled",
|
||||
"columnName": "is_tunnel_on_unsecure_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "wifiDetectionMethod",
|
||||
"columnName": "wifi_detection_method",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "startOnBoot",
|
||||
"columnName": "start_on_boot",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "disableTunnelOnCaptivePortal",
|
||||
"columnName": "disable_on_captive_portal",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "1"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "monitoring_settings",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_local_logs_enabled` INTEGER NOT NULL DEFAULT 0, `tunnel_statistics_enabled` INTEGER NOT NULL DEFAULT 1, `tunnel_statistics_poll_interval` INTEGER NOT NULL DEFAULT 3)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isLocalLogsEnabled",
|
||||
"columnName": "is_local_logs_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "tunnelStatisticsEnabled",
|
||||
"columnName": "tunnel_statistics_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "1"
|
||||
},
|
||||
{
|
||||
"fieldPath": "tunnelStatisticsPollInterval",
|
||||
"columnName": "tunnel_statistics_poll_interval",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "3"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "dns_settings",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `dns_protocol` INTEGER NOT NULL DEFAULT 0, `dns_endpoint` TEXT, `global_tunnel_dns_enabled` INTEGER NOT NULL DEFAULT 0)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "dnsProtocol",
|
||||
"columnName": "dns_protocol",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "dnsEndpoint",
|
||||
"columnName": "dns_endpoint",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isGlobalTunnelDnsEnabled",
|
||||
"columnName": "global_tunnel_dns_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "lockdown_settings",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `bypass_lan` INTEGER NOT NULL DEFAULT 0, `metered` INTEGER NOT NULL DEFAULT 0, `dual_stack` INTEGER NOT NULL DEFAULT 0)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "bypassLan",
|
||||
"columnName": "bypass_lan",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "metered",
|
||||
"columnName": "metered",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "dualStack",
|
||||
"columnName": "dual_stack",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'fd4803fc483f41704303be9246dcfb4d')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -156,7 +156,7 @@
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="@string/provider"
|
||||
android:authorities="${providerAuthority}"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
@@ -177,7 +177,7 @@
|
||||
<service
|
||||
android:name=".service.tile.TunnelControlTile"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_qs_logo"
|
||||
android:icon="@drawable/qs_logo"
|
||||
android:label="@string/tunnel_control"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
<meta-data
|
||||
@@ -194,7 +194,7 @@
|
||||
<service
|
||||
android:name=".service.tile.AutoTunnelControlTile"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_qs_logo"
|
||||
android:icon="@drawable/qs_logo"
|
||||
android:label="@string/auto_tunnel"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
<meta-data
|
||||
|
||||
@@ -48,47 +48,51 @@ class RemoteControlReceiver : BroadcastReceiver(), KoinComponent {
|
||||
}
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
|
||||
val action = intent.action ?: return
|
||||
val appAction = Action.fromAction(action) ?: return
|
||||
|
||||
val pendingResult = goAsync()
|
||||
applicationScope.launch {
|
||||
val settings = settingsRepository.getGeneralSettings()
|
||||
try {
|
||||
val action = intent.action ?: return@launch
|
||||
val appAction = Action.fromAction(action) ?: return@launch
|
||||
|
||||
if (!settings.isRemoteControlEnabled) return@launch
|
||||
val settings = settingsRepository.getGeneralSettings()
|
||||
|
||||
if (!validateKey(settings, intent)) return@launch
|
||||
if (!settings.isRemoteControlEnabled) return@launch
|
||||
|
||||
when (appAction) {
|
||||
Action.START_TUNNEL -> {
|
||||
val tunnel =
|
||||
resolveTunnel(intent)
|
||||
?: tunnelsRepository.getDefaultTunnel()
|
||||
?: return@launch
|
||||
if (!validateKey(settings, intent)) return@launch
|
||||
|
||||
tunnelCoordinator.startTunnel(tunnel)
|
||||
}
|
||||
when (appAction) {
|
||||
Action.START_TUNNEL -> {
|
||||
val tunnel =
|
||||
resolveTunnel(intent)
|
||||
?: tunnelsRepository.getDefaultTunnel()
|
||||
?: return@launch
|
||||
|
||||
Action.STOP_TUNNEL -> {
|
||||
val tunnelName = intent.getStringExtra(EXTRA_TUN_NAME)
|
||||
|
||||
if (tunnelName == null) {
|
||||
tunnelCoordinator.stopActiveTunnels()
|
||||
return@launch
|
||||
tunnelCoordinator.startTunnel(tunnel)
|
||||
}
|
||||
|
||||
val tunnel = tunnelsRepository.findByTunnelName(tunnelName) ?: return@launch
|
||||
Action.STOP_TUNNEL -> {
|
||||
val tunnelName = intent.getStringExtra(EXTRA_TUN_NAME)
|
||||
|
||||
tunnelCoordinator.stopTunnel(tunnel.id)
|
||||
}
|
||||
if (tunnelName == null) {
|
||||
tunnelCoordinator.stopActiveTunnels()
|
||||
return@launch
|
||||
}
|
||||
|
||||
Action.START_AUTO_TUNNEL -> {
|
||||
autoTunnelCoordinator.enable()
|
||||
}
|
||||
val tunnel = tunnelsRepository.findByTunnelName(tunnelName) ?: return@launch
|
||||
|
||||
Action.STOP_AUTO_TUNNEL -> {
|
||||
autoTunnelCoordinator.disable()
|
||||
tunnelCoordinator.stopTunnel(tunnel.id)
|
||||
}
|
||||
|
||||
Action.START_AUTO_TUNNEL -> {
|
||||
autoTunnelCoordinator.enable()
|
||||
}
|
||||
|
||||
Action.STOP_AUTO_TUNNEL -> {
|
||||
autoTunnelCoordinator.disable()
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
pendingResult.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,19 +27,31 @@ class RestartReceiver : BroadcastReceiver(), KoinComponent {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
Timber.d("RestartReceiver triggered with action: ${intent.action}")
|
||||
|
||||
val pendingResult = goAsync()
|
||||
|
||||
applicationScope.launch {
|
||||
when (intent.action) {
|
||||
Intent.ACTION_BOOT_COMPLETED,
|
||||
"android.intent.action.QUICKBOOT_POWERON",
|
||||
"com.htc.intent.action.QUICKBOOT_POWERON" -> {
|
||||
startupCoordinator.applyStartupPolicy()
|
||||
}
|
||||
Intent.ACTION_MY_PACKAGE_REPLACED -> {
|
||||
Timber.i("Restoring state on package upgrade")
|
||||
startupCoordinator.applyStartupPolicy()
|
||||
logReader.deleteAndClearLogs()
|
||||
appStateRepository.setShouldShowDonationSnackbar(true)
|
||||
try {
|
||||
when (intent.action) {
|
||||
Intent.ACTION_BOOT_COMPLETED,
|
||||
"android.intent.action.QUICKBOOT_POWERON",
|
||||
"com.htc.intent.action.QUICKBOOT_POWERON" -> {
|
||||
startupCoordinator.applyStartupPolicy()
|
||||
}
|
||||
|
||||
Intent.ACTION_MY_PACKAGE_REPLACED -> {
|
||||
Timber.i("Restoring state on package upgrade")
|
||||
startupCoordinator.applyStartupPolicy()
|
||||
logReader.deleteAndClearLogs()
|
||||
appStateRepository.setShouldShowDonationSnackbar(true)
|
||||
}
|
||||
|
||||
else -> {
|
||||
Timber.w("Unhandled action in RestartReceiver: ${intent.action}")
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
pendingResult.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ class AppBoostrapCoordinator(
|
||||
listOf(
|
||||
async { bootstrapDns() },
|
||||
async { ensureGlobalConfig() },
|
||||
async { restoreLockdown() },
|
||||
async { restoreBackendConfiguration() },
|
||||
)
|
||||
|
||||
try {
|
||||
@@ -73,9 +73,13 @@ class AppBoostrapCoordinator(
|
||||
tunnelRepository.ensureGlobalConfigExists()
|
||||
}
|
||||
|
||||
private suspend fun restoreLockdown() {
|
||||
private suspend fun restoreBackendConfiguration() {
|
||||
val settings = settingsRepository.getGeneralSettings()
|
||||
|
||||
if (settings.seamlessRoamingEnabled) {
|
||||
tunnelProvider.setSeamlessRoaming(true)
|
||||
}
|
||||
|
||||
when (settings.tunnelMode) {
|
||||
TunnelMode.LOCK_DOWN -> {
|
||||
val lockdownSettings = lockdownRepository.getLockdownSettings()
|
||||
|
||||
@@ -5,7 +5,7 @@ import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelMode
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.GeneralSettingRepository
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.LockdownSettingsRepository
|
||||
|
||||
class TunnelModeCoordinator(
|
||||
class TunnelBackendCoordinator(
|
||||
private val tunnelProvider: TunnelProvider,
|
||||
private val settingsRepository: GeneralSettingRepository,
|
||||
private val lockdownRepository: LockdownSettingsRepository,
|
||||
@@ -42,7 +42,6 @@ class TunnelModeCoordinator(
|
||||
when (newMode) {
|
||||
TunnelMode.LOCK_DOWN -> {
|
||||
val lockdownSettings = lockdownRepository.getLockdownSettings()
|
||||
|
||||
tunnelProvider.setLockDown(lockdownSettings).getOrThrow()
|
||||
}
|
||||
|
||||
@@ -50,4 +49,9 @@ class TunnelModeCoordinator(
|
||||
TunnelMode.PROXY -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun changeSeamlessRoaming(enabled: Boolean) {
|
||||
tunnelProvider.setSeamlessRoaming(enabled).getOrThrow()
|
||||
settingsRepository.updateSeamlessRoaming(enabled)
|
||||
}
|
||||
}
|
||||
@@ -49,4 +49,8 @@ class TunnelBackendProvider(
|
||||
override suspend fun disableLockDown(): Result<Unit> {
|
||||
return backend.disableKillSwitch()
|
||||
}
|
||||
|
||||
override suspend fun setSeamlessRoaming(enabled: Boolean): Result<Unit> {
|
||||
return backend.setSeamlessRoaming(enabled)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ interface TunnelProvider {
|
||||
|
||||
suspend fun disableLockDown(): Result<Unit>
|
||||
|
||||
suspend fun setSeamlessRoaming(enabled: Boolean): Result<Unit>
|
||||
|
||||
val backendStatus: StateFlow<BackendStatus>
|
||||
|
||||
val events: Flow<TunnelEvent>
|
||||
|
||||
@@ -34,7 +34,7 @@ import com.zaneschepke.wireguardautotunnel.data.entity.TunnelConfig
|
||||
DnsSettings::class,
|
||||
LockdownSettings::class,
|
||||
],
|
||||
version = 31,
|
||||
version = 32,
|
||||
autoMigrations =
|
||||
[
|
||||
AutoMigration(from = 1, to = 2),
|
||||
@@ -64,6 +64,7 @@ import com.zaneschepke.wireguardautotunnel.data.entity.TunnelConfig
|
||||
AutoMigration(from = 27, to = 28, spec = DonationMigration::class),
|
||||
AutoMigration(from = 29, to = 30, spec = SingleConfigMigration::class),
|
||||
AutoMigration(from = 30, to = 31),
|
||||
AutoMigration(from = 31, to = 32),
|
||||
],
|
||||
exportSchema = true,
|
||||
)
|
||||
|
||||
@@ -34,4 +34,7 @@ interface GeneralSettingsDao {
|
||||
|
||||
@Query("UPDATE general_settings SET screen_recording_security = :enabled")
|
||||
suspend fun updateScreenRecordingSecurity(enabled: Boolean)
|
||||
|
||||
@Query("UPDATE general_settings SET seamless_roaming_enabled = :enabled")
|
||||
suspend fun updateSeamlessRoaming(enabled: Boolean)
|
||||
}
|
||||
|
||||
@@ -34,4 +34,6 @@ data class GeneralSettings(
|
||||
val isGlobalAmneziaEnabled: Boolean = false,
|
||||
@ColumnInfo(name = "tunnel_scripting_enabled", defaultValue = "0")
|
||||
val tunnelScriptingEnabled: Boolean = true,
|
||||
@ColumnInfo(name = "seamless_roaming_enabled", defaultValue = "0")
|
||||
val seamlessRoamingEnabled: Boolean = true,
|
||||
)
|
||||
|
||||
@@ -16,6 +16,7 @@ fun Entity.toDomain(): Domain =
|
||||
isTunnelOnUnsecureEnabled = isTunnelOnUnsecureEnabled,
|
||||
wifiDetectionMethod = wifiDetectionMethod,
|
||||
startOnBoot = startOnBoot,
|
||||
disableTunnelOnCaptivePortal = disableTunnelOnCaptivePortal
|
||||
)
|
||||
|
||||
fun Domain.toEntity(): Entity =
|
||||
@@ -31,4 +32,5 @@ fun Domain.toEntity(): Entity =
|
||||
isTunnelOnUnsecureEnabled = isTunnelOnUnsecureEnabled,
|
||||
wifiDetectionMethod = wifiDetectionMethod,
|
||||
startOnBoot = startOnBoot,
|
||||
disableTunnelOnCaptivePortal = disableTunnelOnCaptivePortal
|
||||
)
|
||||
|
||||
@@ -22,6 +22,7 @@ fun Entity.toDomain(): Domain =
|
||||
screenRecordingSecurityEnabled = screenRecordingSecurityEnabled,
|
||||
isGlobalAmneziaEnabled = isGlobalAmneziaEnabled,
|
||||
tunnelScriptingEnabled = tunnelScriptingEnabled,
|
||||
seamlessRoamingEnabled = seamlessRoamingEnabled,
|
||||
)
|
||||
|
||||
fun Domain.toEntity(): Entity =
|
||||
@@ -42,4 +43,5 @@ fun Domain.toEntity(): Entity =
|
||||
screenRecordingSecurityEnabled = screenRecordingSecurityEnabled,
|
||||
isGlobalAmneziaEnabled = isGlobalAmneziaEnabled,
|
||||
tunnelScriptingEnabled = tunnelScriptingEnabled,
|
||||
seamlessRoamingEnabled = seamlessRoamingEnabled,
|
||||
)
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
package com.zaneschepke.wireguardautotunnel.data.network
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.BuildConfig
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.okhttp.OkHttp
|
||||
import io.ktor.client.plugins.DefaultRequest
|
||||
import io.ktor.client.plugins.HttpTimeout
|
||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||
import io.ktor.http.HttpHeaders
|
||||
import io.ktor.serialization.kotlinx.json.json
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
object KtorClient {
|
||||
fun create(): HttpClient {
|
||||
return HttpClient(OkHttp) {
|
||||
install(DefaultRequest) {
|
||||
headers {
|
||||
append(HttpHeaders.UserAgent, "wgtunnel/${BuildConfig.VERSION_NAME} (Android)")
|
||||
append(HttpHeaders.Accept, "*/*")
|
||||
}
|
||||
}
|
||||
|
||||
install(ContentNegotiation) {
|
||||
json(
|
||||
Json {
|
||||
@@ -18,10 +28,11 @@ object KtorClient {
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
install(HttpTimeout) {
|
||||
requestTimeoutMillis = 15000
|
||||
connectTimeoutMillis = 15000
|
||||
socketTimeoutMillis = 15000
|
||||
requestTimeoutMillis = 120_000L
|
||||
connectTimeoutMillis = 30_000L
|
||||
socketTimeoutMillis = 120_000L
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,4 +45,8 @@ class RoomSettingsRepository(private val settingsDao: GeneralSettingsDao) :
|
||||
override suspend fun updateScreenRecordingSecurity(enabled: Boolean) {
|
||||
settingsDao.updateScreenRecordingSecurity(enabled)
|
||||
}
|
||||
|
||||
override suspend fun updateSeamlessRoaming(enabled: Boolean) {
|
||||
settingsDao.updateSeamlessRoaming(enabled)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import com.zaneschepke.wireguardautotunnel.util.network.NetworkUtils
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AutoTunnelViewModel
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.ConfigEditViewModel
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.DnsViewModel
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.LicenseViewModel
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.LockdownViewModel
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.LoggerViewModel
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.MonitoringViewModel
|
||||
@@ -80,7 +79,6 @@ val appModule = module {
|
||||
viewModelOf(::AutoTunnelViewModel)
|
||||
viewModel { (id: Int?) -> ConfigEditViewModel(get(), get(), get(), get(), get(), id) }
|
||||
viewModelOf(::DnsViewModel)
|
||||
viewModelOf(::LicenseViewModel)
|
||||
viewModelOf(::LockdownViewModel)
|
||||
viewModelOf(::LoggerViewModel)
|
||||
viewModelOf(::MonitoringViewModel)
|
||||
|
||||
@@ -5,15 +5,15 @@ import com.zaneschepke.wireguardautotunnel.core.orchestration.AutoTunnelCoordina
|
||||
import com.zaneschepke.wireguardautotunnel.core.orchestration.DnsSettingsCoordinator
|
||||
import com.zaneschepke.wireguardautotunnel.core.orchestration.ShortcutCoordinator
|
||||
import com.zaneschepke.wireguardautotunnel.core.orchestration.StartupCoordinator
|
||||
import com.zaneschepke.wireguardautotunnel.core.orchestration.TunnelBackendCoordinator
|
||||
import com.zaneschepke.wireguardautotunnel.core.orchestration.TunnelCoordinator
|
||||
import com.zaneschepke.wireguardautotunnel.core.orchestration.TunnelModeCoordinator
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
import org.koin.core.qualifier.named
|
||||
import org.koin.dsl.module
|
||||
|
||||
val coordinatorModule = module {
|
||||
singleOf(::ShortcutCoordinator)
|
||||
singleOf(::TunnelModeCoordinator)
|
||||
singleOf(::TunnelBackendCoordinator)
|
||||
singleOf(::StartupCoordinator)
|
||||
singleOf(::AutoTunnelCoordinator)
|
||||
singleOf(::DnsSettingsCoordinator)
|
||||
|
||||
@@ -20,5 +20,6 @@ data class GeneralSettings(
|
||||
val alreadyDonated: Boolean = false,
|
||||
val screenRecordingSecurityEnabled: Boolean = true,
|
||||
val isGlobalAmneziaEnabled: Boolean = false,
|
||||
val tunnelScriptingEnabled: Boolean = true,
|
||||
val tunnelScriptingEnabled: Boolean = false,
|
||||
val seamlessRoamingEnabled: Boolean = false,
|
||||
)
|
||||
|
||||
@@ -23,4 +23,6 @@ interface GeneralSettingRepository {
|
||||
suspend fun updateGlobalAmneziaEnabled(enabled: Boolean)
|
||||
|
||||
suspend fun updateScreenRecordingSecurity(enabled: Boolean)
|
||||
|
||||
suspend fun updateSeamlessRoaming(enabled: Boolean)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.ViewQuilt
|
||||
import androidx.compose.material.icons.outlined.Android
|
||||
import androidx.compose.material.icons.outlined.CellWifi
|
||||
import androidx.compose.material.icons.outlined.Dns
|
||||
import androidx.compose.material.icons.outlined.ExpandMore
|
||||
import androidx.compose.material.icons.outlined.MonitorHeart
|
||||
@@ -234,6 +235,23 @@ fun SettingsScreen(
|
||||
viewModel.setTunnelScriptedEnabled(!uiState.settings.tunnelScriptingEnabled)
|
||||
},
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.CellWifi, contentDescription = null) },
|
||||
title = stringResource(R.string.seamless_roaming),
|
||||
trailing = { modifier ->
|
||||
ThemedSwitch(
|
||||
checked = uiState.settings.seamlessRoamingEnabled,
|
||||
onClick = { viewModel.setSeamlessNetworkRoaming(enabled = it) },
|
||||
modifier = modifier,
|
||||
)
|
||||
},
|
||||
description = {
|
||||
DescriptionText(stringResource(R.string.seamless_roaming_description))
|
||||
},
|
||||
onClick = {
|
||||
viewModel.setSeamlessNetworkRoaming(!uiState.settings.seamlessRoamingEnabled)
|
||||
},
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.MonitorHeart, null) },
|
||||
title = stringResource(R.string.tunnel_monitoring),
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.support.license
|
||||
|
||||
import android.content.Context
|
||||
import com.mikepenz.aboutlibraries.Libs
|
||||
import com.mikepenz.aboutlibraries.entity.Developer
|
||||
import com.mikepenz.aboutlibraries.entity.Library
|
||||
import com.mikepenz.aboutlibraries.entity.License
|
||||
import com.mikepenz.aboutlibraries.entity.Scm
|
||||
import com.mikepenz.aboutlibraries.util.withContext
|
||||
|
||||
fun buildLibsWithAdditionalLibraries(context: Context): Libs {
|
||||
val baseLibs = Libs.Builder().withContext(context).build()
|
||||
|
||||
val cleanedBaseLibs =
|
||||
baseLibs.libraries.filterNot { library ->
|
||||
library.uniqueId.contains("com.github.topjohnwu.libsu", ignoreCase = true) ||
|
||||
library.uniqueId.contains("com.github.T8RIN.QuickieExtended", ignoreCase = true)
|
||||
}
|
||||
|
||||
val nativeLibraries =
|
||||
listOf(
|
||||
Library(
|
||||
uniqueId = "github.com.wgtunnel:amneziawg-go",
|
||||
artifactVersion = "v0.0.0-20260618075902-e1b699b2104b",
|
||||
name = "AmneziaWG Go (Fork)",
|
||||
description = "WireGuard implementation with Amnezia obfuscation",
|
||||
website = "https://wgtunnel.com",
|
||||
developers =
|
||||
listOf(
|
||||
Developer(
|
||||
name = "Zane Schepke (Fork Maintainer)",
|
||||
organisationUrl = "https://wgtunnel.com",
|
||||
),
|
||||
Developer(
|
||||
name = "Jason A. Donenfeld (Original WireGuard)",
|
||||
organisationUrl = "https://www.wireguard.com/",
|
||||
),
|
||||
Developer(
|
||||
name = "Amnezia VPN Team",
|
||||
organisationUrl = "https://amnezia.org/",
|
||||
),
|
||||
),
|
||||
organization = null,
|
||||
scm = Scm(null, null, "https://github.com/wgtunnel/amneziawg-go"),
|
||||
licenses =
|
||||
setOf(
|
||||
License(
|
||||
name = "MIT License",
|
||||
url = "https://opensource.org/licenses/MIT",
|
||||
spdxId = "MIT",
|
||||
hash = "mit-license-amneziawg-fork",
|
||||
)
|
||||
),
|
||||
funding = emptySet(),
|
||||
tag = "native",
|
||||
),
|
||||
Library(
|
||||
uniqueId = "github.com.wgtunnel:wireproxy-awg",
|
||||
artifactVersion = "v0.0.0-20260309043206-ff4200f20ff2",
|
||||
name = "Wireproxy AWG (Fork)",
|
||||
description = "WireGuard proxy with Amnezia support",
|
||||
website = "https://wgtunnel.com",
|
||||
developers =
|
||||
listOf(
|
||||
Developer(
|
||||
name = "Zane Schepke (Fork Maintainer)",
|
||||
organisationUrl = "https://wgtunnel.com",
|
||||
),
|
||||
Developer(name = "Artem Russkikh (Original)", organisationUrl = null),
|
||||
),
|
||||
organization = null,
|
||||
scm = Scm(null, null, "https://github.com/wgtunnel/wireproxy-awg"),
|
||||
licenses =
|
||||
setOf(
|
||||
License(
|
||||
name = "MIT License",
|
||||
url = "https://opensource.org/licenses/MIT",
|
||||
spdxId = "MIT",
|
||||
hash = "mit-license-wireproxy-fork",
|
||||
)
|
||||
),
|
||||
funding = emptySet(),
|
||||
tag = "native",
|
||||
),
|
||||
Library(
|
||||
uniqueId = "github.com.wgtunnel:go-socks5",
|
||||
artifactVersion = "v0.0.0-20260307052555-86f8d93b9534",
|
||||
name = "go-socks5 (Fork)",
|
||||
description = "SOCKS5 proxy server implementation",
|
||||
website = "https://wgtunnel.com",
|
||||
developers =
|
||||
listOf(
|
||||
Developer(
|
||||
name = "Zane Schepke (Fork Maintainer)",
|
||||
organisationUrl = "https://wgtunnel.com",
|
||||
),
|
||||
Developer(name = "Things-go Team (Original)", organisationUrl = null),
|
||||
),
|
||||
organization = null,
|
||||
scm = Scm(null, null, "https://github.com/wgtunnel/go-socks5"),
|
||||
licenses =
|
||||
setOf(
|
||||
License(
|
||||
name = "MIT License",
|
||||
url = "https://opensource.org/licenses/MIT",
|
||||
spdxId = "MIT",
|
||||
hash = "mit-license-go-socks5-fork",
|
||||
)
|
||||
),
|
||||
funding = emptySet(),
|
||||
tag = "native",
|
||||
),
|
||||
Library(
|
||||
uniqueId = "github.com.miekg:dns",
|
||||
artifactVersion = "v1.1.69",
|
||||
name = "miekg/dns",
|
||||
description = "DNS library for Go",
|
||||
website = "https://github.com/miekg/dns",
|
||||
developers = listOf(Developer(name = "Miek Gieben", organisationUrl = null)),
|
||||
organization = null,
|
||||
scm = Scm(null, null, "https://github.com/miekg/dns"),
|
||||
licenses =
|
||||
setOf(
|
||||
License(
|
||||
name = "BSD 3-Clause \"New\" or \"Revised\" License",
|
||||
url = "https://opensource.org/licenses/BSD-3-Clause",
|
||||
spdxId = "BSD-3-Clause",
|
||||
hash = "bsd3-miekg-dns",
|
||||
)
|
||||
),
|
||||
funding = emptySet(),
|
||||
tag = "go",
|
||||
),
|
||||
Library(
|
||||
uniqueId = "github.com.heiher:hev-socks5-tunnel",
|
||||
artifactVersion = "2.15.0",
|
||||
name = "hev-socks5-tunnel",
|
||||
description = "High performance SOCKS5 tunnel",
|
||||
website = "https://github.com/heiher/hev-socks5-tunnel",
|
||||
developers = listOf(Developer(name = "heiher", organisationUrl = null)),
|
||||
organization = null,
|
||||
scm = Scm(null, null, "https://github.com/heiher/hev-socks5-tunnel"),
|
||||
licenses =
|
||||
setOf(
|
||||
License(
|
||||
name = "MIT License",
|
||||
url = "https://opensource.org/licenses/MIT",
|
||||
spdxId = "MIT",
|
||||
hash = "mit-license-hev",
|
||||
)
|
||||
),
|
||||
funding = emptySet(),
|
||||
tag = "native",
|
||||
),
|
||||
)
|
||||
|
||||
val additionalLibraries =
|
||||
listOf(
|
||||
Library(
|
||||
uniqueId = "com.github.T8RIN.QuickieExtended:quickie-foss",
|
||||
artifactVersion = "1.18.1",
|
||||
name = "QuickieFoss",
|
||||
description = "Camera QR code scanner",
|
||||
website = "https://github.com/T8RIN/QuickieExtended",
|
||||
developers = listOf(Developer(name = "T8RIN", null)),
|
||||
organization = null,
|
||||
scm = Scm(null, null, "https://github.com/T8RIN/QuickieExtended"),
|
||||
licenses =
|
||||
setOf(
|
||||
License(
|
||||
name = "Apache License 2.0",
|
||||
url = "https://www.apache.org/licenses/LICENSE-2.0",
|
||||
spdxId = "Apache-2.0",
|
||||
hash = "apache-2-quickie",
|
||||
)
|
||||
),
|
||||
funding = emptySet(),
|
||||
tag = "ui",
|
||||
),
|
||||
Library(
|
||||
uniqueId = "com.github.topjohnwu.libsu:core",
|
||||
artifactVersion = "6.0.0",
|
||||
name = "libsu",
|
||||
description = "Root shell library for Android",
|
||||
website = "https://github.com/topjohnwu/libsu",
|
||||
developers = listOf(Developer(name = "topjohnwu", null)),
|
||||
organization = null,
|
||||
scm = Scm(null, null, "https://github.com/topjohnwu/libsu"),
|
||||
licenses =
|
||||
setOf(
|
||||
License(
|
||||
name = "Apache License 2.0",
|
||||
url = "https://www.apache.org/licenses/LICENSE-2.0",
|
||||
spdxId = "Apache-2.0",
|
||||
hash = "apache-2-libsu",
|
||||
)
|
||||
),
|
||||
funding = emptySet(),
|
||||
tag = "system",
|
||||
),
|
||||
)
|
||||
|
||||
return Libs(
|
||||
libraries =
|
||||
(cleanedBaseLibs + nativeLibraries + additionalLibraries).sortedBy {
|
||||
it.name.lowercase()
|
||||
},
|
||||
licenses =
|
||||
baseLibs.licenses +
|
||||
nativeLibraries.flatMap { it.licenses } +
|
||||
additionalLibraries.flatMap { it.licenses },
|
||||
)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class LicenseFileEntry(
|
||||
val groupId: String,
|
||||
val artifactId: String,
|
||||
val version: String,
|
||||
val name: String? = null,
|
||||
val spdxLicenses: List<SpdxLicense> = emptyList(),
|
||||
val scm: Scm? = null,
|
||||
)
|
||||
|
||||
@Serializable data class SpdxLicense(val identifier: String, val name: String, val url: String)
|
||||
|
||||
@Serializable data class Scm(val url: String)
|
||||
@@ -1,30 +1,23 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.support.license
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.CircularWavyProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.license.components.LicenseList
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.LicenseViewModel
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import org.orbitmvi.orbit.compose.collectAsState
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
|
||||
import com.mikepenz.aboutlibraries.ui.compose.variant.LibraryDetailMode
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun LicenseScreen(viewModel: LicenseViewModel = koinViewModel()) {
|
||||
val licenseUiState by viewModel.collectAsState()
|
||||
fun LicenseScreen() {
|
||||
val context = LocalContext.current
|
||||
val libs = remember { buildLibsWithAdditionalLibraries(context) }
|
||||
|
||||
if (licenseUiState.isLoading) {
|
||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
CircularWavyProgressIndicator(waveSpeed = 60.dp, modifier = Modifier.size(48.dp))
|
||||
}
|
||||
} else {
|
||||
LicenseList(licenseUiState.licenses)
|
||||
}
|
||||
LibrariesContainer(
|
||||
libraries = libs,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
detailMode = LibraryDetailMode.Sheet,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.support.license.components
|
||||
|
||||
import LicenseFileEntry
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.Launch
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.scrollbar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||
|
||||
@Composable
|
||||
fun LicenseList(licenses: List<LicenseFileEntry>) {
|
||||
val context = LocalContext.current
|
||||
val lazyListState = rememberLazyListState()
|
||||
|
||||
LazyColumn(
|
||||
modifier =
|
||||
Modifier.fillMaxSize()
|
||||
.scrollbar(
|
||||
state = lazyListState.scrollIndicatorState,
|
||||
orientation = Orientation.Vertical,
|
||||
),
|
||||
state = lazyListState,
|
||||
) {
|
||||
items(licenses) { entry ->
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier =
|
||||
Modifier.clickable(enabled = entry.scm?.url != null) {
|
||||
entry.scm?.url?.let { context.openWebUrl(it) }
|
||||
}
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = "${entry.artifactId} (${entry.version})",
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
)
|
||||
|
||||
entry.spdxLicenses.forEach { license ->
|
||||
Text(
|
||||
text = license.name,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
entry.scm?.url?.let { Icon(Icons.AutoMirrored.Outlined.Launch, null) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.state
|
||||
|
||||
import LicenseFileEntry
|
||||
|
||||
data class LicenseUiState(
|
||||
val isLoading: Boolean = true,
|
||||
val licenses: List<LicenseFileEntry> = emptyList(),
|
||||
)
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.zaneschepke.wireguardautotunnel.util
|
||||
|
||||
import LicenseFileEntry
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
@@ -23,7 +22,6 @@ import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.Json
|
||||
import timber.log.Timber
|
||||
|
||||
class FileUtils(private val context: Context, private val ioDispatcher: CoroutineDispatcher) {
|
||||
@@ -293,15 +291,6 @@ class FileUtils(private val context: Context, private val ioDispatcher: Coroutin
|
||||
return@withContext itemUri
|
||||
}
|
||||
|
||||
suspend fun readLibraryLicensesFromAssets(): List<LicenseFileEntry> =
|
||||
withContext(ioDispatcher) {
|
||||
val json = Json { ignoreUnknownKeys = true }
|
||||
|
||||
val jsonResult =
|
||||
context.assets.open("licenses.json").bufferedReader().use { it.readText() }
|
||||
json.decodeFromString(jsonResult)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CONF_FILE_EXTENSION = ".conf"
|
||||
const val ZIP_FILE_EXTENSION = ".zip"
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.os.Build
|
||||
import android.provider.Settings
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.net.toUri
|
||||
import com.zaneschepke.wireguardautotunnel.BuildConfig
|
||||
import com.zaneschepke.wireguardautotunnel.MainActivity
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
@@ -148,7 +149,7 @@ fun Context.requestInstallPackagesPermission() {
|
||||
}
|
||||
|
||||
fun Context.installApk(apkFile: File) {
|
||||
val apkUri = FileProvider.getUriForFile(this, getString(R.string.provider), apkFile)
|
||||
val apkUri = FileProvider.getUriForFile(this, BuildConfig.FILE_PROVIDER_AUTHORITY, apkFile)
|
||||
val intent =
|
||||
Intent(Intent.ACTION_VIEW).apply {
|
||||
setDataAndType(apkUri, "application/vnd.android.package-archive")
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.LicenseUiState
|
||||
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import org.orbitmvi.orbit.ContainerHost
|
||||
import org.orbitmvi.orbit.viewmodel.container
|
||||
|
||||
class LicenseViewModel(private val fileUtils: FileUtils) :
|
||||
ContainerHost<LicenseUiState, Nothing>, ViewModel() {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override val container =
|
||||
container<LicenseUiState, Nothing>(
|
||||
LicenseUiState(),
|
||||
buildSettings = { repeatOnSubscribedStopTimeout = 5000L },
|
||||
) {
|
||||
intent {
|
||||
val licenses = fileUtils.readLibraryLicensesFromAssets()
|
||||
reduce { state.copy(isLoading = false, licenses = licenses) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
|
||||
import com.dokar.sonner.ToastType
|
||||
import com.zaneschepke.tunnel.util.RootShell
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.core.orchestration.TunnelBackendCoordinator
|
||||
import com.zaneschepke.wireguardautotunnel.core.orchestration.TunnelCoordinator
|
||||
import com.zaneschepke.wireguardautotunnel.core.shortcut.ShortcutManager
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.GeneralSettingRepository
|
||||
@@ -27,6 +28,7 @@ class SettingsViewModel(
|
||||
private val monitoringRepository: MonitoringSettingsRepository,
|
||||
private val globalEffectRepository: GlobalEffectRepository,
|
||||
private val tunnelCoordinator: TunnelCoordinator,
|
||||
private val tunnelBackendCoordinator: TunnelBackendCoordinator,
|
||||
) : ContainerHost<SettingUiState, Nothing>, ViewModel() {
|
||||
|
||||
override val container =
|
||||
@@ -114,6 +116,10 @@ class SettingsViewModel(
|
||||
settingsRepository.upsert(state.settings.copy(tunnelScriptingEnabled = to))
|
||||
}
|
||||
|
||||
fun setSeamlessNetworkRoaming(enabled: Boolean) = intent {
|
||||
tunnelBackendCoordinator.changeSeamlessRoaming(enabled)
|
||||
}
|
||||
|
||||
fun setAlreadyDonated(to: Boolean) = intent {
|
||||
settingsRepository.upsert(state.settings.copy(alreadyDonated = to))
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.dokar.sonner.ToastType
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.core.orchestration.TunnelBackendCoordinator
|
||||
import com.zaneschepke.wireguardautotunnel.core.orchestration.TunnelCoordinator
|
||||
import com.zaneschepke.wireguardautotunnel.core.orchestration.TunnelModeCoordinator
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelMode
|
||||
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppStateRepository
|
||||
@@ -61,7 +61,7 @@ class SharedAppViewModel(
|
||||
private val settingsRepository: GeneralSettingRepository,
|
||||
private val autoTunnelStateHolder: AutoTunnelStateHolder,
|
||||
private val selectedTunnelsRepository: SelectedTunnelsRepository,
|
||||
private val tunnelModeCoordinator: TunnelModeCoordinator,
|
||||
private val tunnelBackendCoordinator: TunnelBackendCoordinator,
|
||||
private val httpClient: HttpClient,
|
||||
private val fileUtils: FileUtils,
|
||||
private val networkUtils: NetworkUtils,
|
||||
@@ -172,7 +172,7 @@ class SharedAppViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
tunnelModeCoordinator.changeMode(mode)
|
||||
tunnelBackendCoordinator.changeMode(mode)
|
||||
}
|
||||
|
||||
fun setShouldShowDonationSnackbar(to: Boolean) = intent {
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<group
|
||||
android:scaleX="1.18"
|
||||
android:scaleY="1.18"
|
||||
android:pivotX="512"
|
||||
android:pivotY="512"
|
||||
android:translateX="-45"
|
||||
android:translateY="-45">
|
||||
<path
|
||||
android:pathData="M779.7,207.8C782.5,207.8 785.4,207.8 788.2,207.8C851.8,207.6 851.8,207.6 871.1,225.5C882.4,237.3 887.6,250.9 887.5,267.1C886.9,284.9 879,300.9 872,317C871,319.2 870.1,321.4 869.1,323.7C867.7,327.1 866.2,330.6 864.7,334.1C861,342.7 857.3,351.4 853.7,360.1C847.1,375.7 840.5,391.3 833.8,406.9C831.1,413.1 828.5,419.2 825.8,425.4C822.9,432.3 819.9,439.2 817,446C804.9,474 804.9,474 793.3,502.2C791,507.8 788.5,513.3 785.9,518.7C782,527 778.5,535.5 775,544C772,551 769,557.9 766.1,564.9C765.6,566 765.1,567.2 764.6,568.4C763.5,570.7 762.5,573.1 761.5,575.5C760,579 758.5,582.6 757,586.1C751.8,598.2 746.6,610.4 741.2,622.4C737.3,631.3 733.4,640.3 729.5,649.2C725.8,657.9 722,666.6 718,675.2C715.7,680.4 713.5,685.6 711.4,690.8C708.5,697.8 705.4,704.7 702.1,711.6C700.8,714.5 699.4,717.4 698,720.4C685.7,746.7 672.9,772.5 643.9,783.1C639.6,784.4 635.5,784.7 631,785C630.2,785.1 629.5,785.1 628.7,785.2C610.7,785.8 596.7,779.5 583.5,767.8C569.4,754.5 562,735.7 554.3,718.3C552.1,713.5 549.9,708.7 547.5,704.1C543.9,696.9 540.8,689.6 537.6,682.2C534.6,675 531.4,667.8 528.3,660.6C522.3,646.9 516.3,633.3 510.4,619.6C508.5,615.1 506.5,610.6 504.6,606.1C494.1,582.1 494.1,582.1 489.6,571.2C488,567.4 486.4,563.6 484.6,559.8C481.8,553.6 479.2,547.4 476.6,541.1C472.4,531.2 472.4,531.2 468,521.5C461.3,507.3 455.5,492.6 449.5,478.1C444.9,466.9 440.2,455.8 435.3,444.7C431.5,436.1 427.7,427.4 424.1,418.8C423.8,418.1 423.5,417.4 423.2,416.6C420,409.1 416.8,401.6 413.7,394C413.4,393.3 413.2,392.7 412.9,392C411.6,389 410.4,386 409.2,383C406.8,377.1 404.3,371.4 401.6,365.7C397.9,357.9 394.6,349.9 391.3,341.9C390.4,339.6 389.5,337.4 388.6,335.2C388,333.7 387.3,332.2 386.7,330.7C384.7,325.7 382.6,320.7 380.6,315.8C380,314.5 379.5,313.2 378.9,311.9C377.9,309.5 376.9,307.1 375.9,304.7C368.4,286.7 361.9,264.8 369.7,245.7C373.8,237.4 377.9,230 385,224C385.6,223.5 386.2,223 386.8,222.4C397,213.9 412.5,209.7 425.8,210.1C442.3,212.1 455.2,220.4 466,233C471.9,241.6 476.5,250.7 480.9,260.1C481.5,261.3 482.2,262.6 482.8,263.9C487.3,273.5 491.7,283.1 496,292.8C497,294.9 497.9,297.1 498.9,299.2C503.6,309.6 508.1,320.1 512.5,330.6C515.1,336.6 517.7,342.5 520.5,348.4C524.1,356.1 527.4,363.9 530.6,371.8C531.7,374.4 532.8,377 533.9,379.7C534.3,380.6 534.3,380.6 534.7,381.6C536.3,385.3 537.8,388.9 539.5,392.5C541.5,396.7 543.3,401 545.1,405.3C545.4,406 545.6,406.6 545.9,407.3C547,409.9 548.1,412.6 549.2,415.2C552.5,423.2 555.9,431.1 559.6,438.9C561.7,443.4 563.7,447.9 565.5,452.5C567.6,457.7 569.8,462.9 572.3,467.9C575.9,475.7 579.2,483.5 582.5,491.4C585.8,499.2 589.1,507 592.5,514.7C617.2,571.5 617.2,571.5 625.1,591.7C625.8,593.9 625.8,593.9 627,595C627.3,594.3 627.6,593.5 627.9,592.7C632.8,580 637.7,567.3 643.4,554.9C646.9,547.2 650.2,539.5 653.4,531.8C653.7,531.1 654,530.4 654.3,529.7C657.2,522.9 660,516 662.9,509.1C663.3,508 663.8,506.9 664.2,505.8C665,503.9 665.9,501.9 666.7,499.9C668.9,494.5 671.3,489.1 673.8,483.7C675.7,479.4 677.6,475 679.5,470.7C679.9,469.7 680.3,468.7 680.8,467.7C682.2,464.5 683.6,461.2 685,458C686,455.6 687.1,453.3 688.1,450.9C689.7,447.1 691.3,443.4 693,439.6C695.8,433.2 698.5,426.8 701.3,420.3C714.6,389.7 714.6,389.7 727.2,358.9C729.7,352.7 732.4,346.8 735.3,340.8C736.9,337.4 738.2,333.8 739.6,330.3C740.1,329.1 740.5,327.9 741,326.7C741.3,325.8 741.6,324.9 742,324C741.2,324 741.2,324 740.3,324C726.5,324.1 712.7,324.1 698.9,324.1C692.2,324.1 685.5,324.1 678.9,324.2C672.4,324.2 665.9,324.2 659.5,324.2C657,324.2 654.6,324.2 652.1,324.2C640.5,324.3 629,324.2 617.4,323.1C616.6,323.1 615.8,323 614.9,322.9C597,321 581.4,314.3 569,301C566.5,297.8 564.7,294.6 563,291C562.5,290 562,289 561.5,288C556.5,276.1 555.8,261.8 559.6,249.4C562,243.5 565.3,238.2 569,233C569.7,232 569.7,232 570.4,231C581.2,217.3 599.1,212.3 615.8,210.2C637.1,208 659,208.7 680.4,208.5C683.5,208.4 686.5,208.4 689.5,208.4C719.6,208.1 749.6,207.9 779.7,207.8Z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
<path
|
||||
android:pathData="M263.3,223.5C276.3,234.5 282.6,248.9 289.6,264C290.9,266.8 292.2,269.5 293.5,272.2C303,291.5 311.4,311.3 320,331C320.3,331.7 320.6,332.5 321,333.2C323.4,338.9 325.9,344.5 328.3,350.2C332.2,359.1 336.1,368.1 340,377C346.2,391.1 352.3,405.2 358.4,419.3C360.8,424.9 363.3,430.5 365.7,436.1C366.5,438 367.3,439.9 368.1,441.7C370.6,447.4 373,453 375.5,458.7C382.5,474.8 389.4,491 396.4,507.2C398.1,511.3 399.9,515.5 401.7,519.6C408.7,536 415.8,552.5 422.7,568.9C424,572 425.3,575.2 426.7,578.3C433,593.2 439.2,608.1 445.5,623C447.5,627.7 449.4,632.4 451.4,637C453.8,642.9 456.3,648.7 458.7,654.6C459.9,657.5 461.2,660.4 462.4,663.3C467.1,674.6 471.8,685.9 476.4,697.3C477.1,698.8 477.1,698.8 477.7,700.4C484.3,716.8 488.7,735.5 482,752.6C479.3,758.3 475.9,763.2 472,768C471.6,768.6 471.1,769.2 470.7,769.8C464.5,777.8 455.6,782.4 446,785C445,785.3 444,785.7 443,786C431.6,787 420.6,786.5 410,782C408.7,781.5 408.7,781.5 407.4,781C380.7,769.4 369.2,736.1 358.2,711.6C356.7,708.3 355.1,705 353.6,701.7C340.8,674.4 329,646.7 317.5,618.9C316,615.2 314.3,611.5 312.7,607.9C309.7,601.4 306.9,594.9 304.1,588.4C303.9,587.8 303.6,587.2 303.3,586.5C299.7,578 296.1,569.4 292.6,560.8C291,556.9 289.2,553 287.4,549.1C284.7,543 282,536.9 279.3,530.8C278.7,529.3 278.1,527.9 277.4,526.5C276.2,523.6 274.9,520.8 273.7,517.9C272.3,514.6 270.8,511.3 269.4,508C264.2,496.1 259.1,484.2 254.1,472.3C250.4,463.6 246.7,455 242.9,446.3C242.5,445.4 242.2,444.5 241.8,443.6C240,439.6 238.3,435.6 236.5,431.6C234.9,427.8 233.2,424.1 231.6,420.4C231.4,419.7 231.1,419.1 230.8,418.5C226.1,407.6 221.5,396.7 217,385.8C214.5,379.6 211.9,373.5 209.2,367.5C207.4,363.7 205.8,359.9 204.1,356.1C203.8,355.2 203.4,354.4 203,353.5C199.1,344.2 195.2,334.9 191.3,325.6C191,324.9 190.7,324.1 190.3,323.3C187.1,315.5 183.8,307.6 180.7,299.8C180.2,298.6 179.7,297.4 179.2,296.2C177.6,291.9 176.2,287.5 175,283C174.8,282.4 174.7,281.8 174.5,281.2C171.2,266.5 173.7,250.9 181.4,238.2C191.3,223.5 203.1,215.7 220.6,212.3C236.3,210.9 250.7,213.5 263.3,223.5Z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,7 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="1024" android:viewportWidth="1024" android:width="24dp">
|
||||
|
||||
<path android:fillColor="#FEFEFE" android:pathData="M654.6,237.7C658.8,237.7 663.1,237.7 667.4,237.7C677.4,237.7 687.5,237.7 697.6,237.7C707.9,237.7 718.2,237.7 728.6,237.7C737.5,237.6 746.4,237.6 755.4,237.6C760.7,237.6 766,237.6 771.3,237.6C776.3,237.6 781.3,237.6 786.3,237.6C788.1,237.6 789.9,237.6 791.7,237.6C807.6,237.5 826.2,238.6 838.3,250.2C849.7,262.5 857,276.4 856.4,293.7C855.7,300 853.9,306 852,312C851.7,313.1 851.3,314.1 851,315.3C845.5,331.6 837.9,347.2 830.5,362.7C825.4,373.2 821,383.7 817.2,394.7C815.8,398.5 814.2,402 812.5,405.6C810.4,410.4 808.5,415.1 806.5,419.9C806.1,420.9 805.7,421.9 805.3,422.9C804.5,424.8 803.8,426.7 803,428.6C799.2,438 795.1,447.3 791.1,456.6C787.3,465.3 783.5,474.1 779.8,483C777.8,487.9 775.7,492.8 773.4,497.6C771.5,501.8 769.8,506 768.1,510.3C765.8,516 763.2,521.7 760.8,527.4C757,536.1 753.2,544.8 749.6,553.5C746.9,560 743.9,566.3 740.7,572.6C736.3,581.3 732.6,590.3 729.1,599.4C728.8,600.4 728.8,600.4 728.4,601.4C727.1,604.8 725.8,608.1 724.5,611.5C721.1,620.5 717.2,629.3 713,638C711.6,641.1 710.2,644.1 708.8,647.2C708.4,648 708.1,648.8 707.7,649.7C704.4,656.9 701.3,664.2 698.2,671.6C690.6,689.4 682.8,707.1 674.6,724.6C673.2,727.6 671.8,730.7 670.5,733.8C661.2,755.7 650.6,777.3 627.3,787.1C619.6,789.9 612.7,790.6 604.4,790.5C603.3,790.5 602.2,790.5 601,790.5C589.3,790.5 580.2,787.1 571,780C570.3,779.4 569.5,778.9 568.8,778.3C557.3,768.9 551.3,756.1 545.3,742.9C543,737.9 540.7,732.8 538.3,727.8C537.5,726 536.7,724.2 535.8,722.4C533,716.3 530.1,710.2 527.2,704.1C523.7,696.8 520.4,689.5 517.5,682C516,678.3 514.4,674.7 512.6,671.2C509.4,665 507,658.5 504.6,651.9C502.5,646.6 500.3,641.4 497.7,636.3C493.6,628.4 490.2,620.2 486.9,611.9C486.3,610.4 485.7,608.9 485.1,607.5C483.9,604.6 482.8,601.8 481.6,598.9C477.4,588.5 473,578.2 468.1,568.1C460.7,552.6 454.6,536.7 448.8,520.7C445.8,512.5 442,504.8 438,497.1C432.2,485.5 427.2,473.8 422.8,461.7C420.1,454.6 417.1,447.7 413.8,440.9C411,435 408.4,428.9 405.8,422.9C404.9,420.9 404,418.8 403.1,416.8C399.9,409.5 396.9,402.3 394.1,394.9C392.6,390.9 390.9,387 389.1,383.1C388.8,382.4 388.4,381.6 388.1,380.8C385.6,375.3 383,369.8 380.4,364.3C378.1,359.3 376.1,354.2 374.2,349C373.1,346.3 371.9,343.8 370.6,341.3C367.3,334.9 365.1,328.1 362.8,321.3C362,319 361.1,316.7 360.3,314.5C354.4,298.6 354.5,284.7 361,269C365.9,258.7 375.2,249.3 385.8,244.7C399.3,240.2 417.7,237.5 431,244C455.6,257.2 466.2,288.1 475.7,312.7C477.3,316.9 479.2,320.8 481.1,324.8C487.7,338.7 493.6,352.9 499.3,367.2C503,376.4 507,385.4 511.3,394.3C517.4,407.2 522.6,420.3 527.7,433.6C531,442.1 534.5,450.5 538.5,458.8C540.3,462.6 541.9,466.5 543.5,470.5C545.9,476.3 548.4,482 551.1,487.7C554.6,495.1 557.7,502.6 560.8,510.1C563.6,516.7 566.5,523.3 569.4,529.8C573.7,539.2 577.6,548.7 581.6,558.2C585.6,568 589.7,577.7 593.9,587.4C595.9,592.2 598,596.9 600,601.6C600.4,602.5 600.8,603.4 601.2,604.3C601.5,605.2 601.9,606 602.3,606.9C602.6,607.6 602.9,608.3 603.2,609.1C603.9,610.7 604.4,612.3 605,614C607.7,608.6 610.3,603.2 612.7,597.7C613.1,597 613.4,596.2 613.8,595.4C617.7,586.5 621.4,577.4 625.1,568.4C631.8,551.7 638.9,535.2 646.4,518.9C649.1,513 651.6,507.1 654,501C657.6,491.9 661.3,482.8 665.1,473.7C665.5,472.7 665.9,471.7 666.3,470.7C668.3,465.9 670.3,461.3 672.7,456.7C674.7,452.8 676.1,449 677.4,444.8C679.4,438.4 682.1,432.6 685.2,426.7C687.2,422.6 688.9,418.5 690.6,414.3C692.8,408.9 695.1,403.7 697.7,398.6C700.3,393.3 702.6,387.8 705,382.4C706.1,379.8 707.2,377.2 708.4,374.6C709.2,372.8 710,370.9 710.8,369.1C711.2,368.2 711.6,367.3 712,366.4C715.1,359.2 717.7,352.9 716,345C714.8,345 713.6,345 712.4,345C701.2,344.9 690,344.8 678.7,344.7C673,344.6 667.2,344.6 661.4,344.5C617.2,344.1 617.2,344.1 600.4,343.6C599.3,343.5 598.2,343.5 597.1,343.5C577.7,342.7 563.5,336.2 550.1,322C544.3,315 540.5,305.1 540,296C539.9,295.3 539.9,294.5 539.8,293.8C539.3,280.1 544.1,267.9 553.3,257.8C578.1,231.3 621.5,237.7 654.6,237.7Z"/>
|
||||
|
||||
<path android:fillColor="#FEFEFE" android:pathData="M253,250C253.7,250.5 254.4,250.9 255.1,251.4C269.1,262.1 275.9,283.6 282.8,299.1C284.1,302 285.4,304.8 286.7,307.7C291.1,316.8 294.9,326.1 298.7,335.5C301.8,343.2 305.1,350.9 308.4,358.5C308.8,359.4 309.2,360.3 309.6,361.2C311.5,365.6 313.5,370 315.5,374.4C318.1,380.2 320.6,386 323,391.9C323.6,393.4 323.6,393.4 324.3,394.9C326.4,400 328.5,405.2 330.4,410.5C332.9,417 335.9,423.2 339,429.4C345.8,443.2 351.7,457.2 357.5,471.4C361,479.9 364.6,488.3 368.5,496.6C369.1,497.8 369.7,499.1 370.3,500.3C371.4,502.7 372.5,505 373.6,507.4C374.1,508.5 374.6,509.5 375.1,510.6C375.6,511.5 376,512.5 376.4,513.4C378.3,517.8 380,522.2 381.7,526.6C383.7,531.7 385.8,536.8 387.9,541.8C388.4,542.9 388.9,544 389.3,545.1C392,551.4 394.7,557.7 397.5,564C397.8,564.7 398.1,565.3 398.4,566C398.9,567 398.9,567 399.3,568.1C399.9,569.4 400.5,570.8 401.1,572.2C401.4,572.9 401.7,573.5 402,574.2C406.2,583.8 410.2,593.4 413.9,603.1C416.1,608.9 418.6,614.5 421.2,620.1C424.6,627.2 427.7,634.3 430.9,641.5C431.3,642.5 431.3,642.5 431.8,643.6C436.1,653.4 440.2,663.3 444,673.4C446.5,679.7 449.2,685.9 452.1,692C472.4,734.7 472.4,734.7 465.3,755.8C459.9,769.4 451,781 437.5,787.2C436.4,787.7 435.3,788.2 434.2,788.7C422.2,793.5 408.1,791.5 396.4,787C369.2,774.6 356.6,742.9 346.5,716.7C344.7,712.4 342.8,708.2 340.8,704C333,687.8 326,671.2 319,654.6C316.1,647.6 313.1,640.6 310.1,633.7C307.7,628.1 305.4,622.5 303.3,616.8C301.6,612.4 299.7,608.2 297.5,604C291.1,591.1 285.6,577.7 280,564.4C277.2,557.7 274.3,551.1 271.4,544.4C268.9,538.9 266.7,533.3 264.6,527.7C263.4,524.8 262,522 260.6,519.2C258,514.1 255.8,508.9 253.7,503.6C252.9,501.6 252.1,499.7 251.3,497.8C250.9,496.8 250.6,495.8 250.2,494.8C248.4,490.6 246.7,486.3 244.9,482.1C244.6,481.4 244.3,480.7 244,480C242.3,475.9 240.5,471.9 238.6,467.9C234.7,459.7 231.2,451.4 227.8,443.1C226.9,440.9 226,438.7 225.1,436.5C222.4,430 219.8,423.5 217.3,417C215.4,411.9 213.2,407 210.8,402.1C203.8,387.5 197.4,372.6 191.3,357.6C189.5,353.1 187.5,348.6 185.4,344.1C182.4,337.5 179.7,330.9 176.9,324.3C176.4,322.9 175.8,321.5 175.2,320.1C168.5,304.1 166.4,288.4 173,272C179.5,258.3 189.7,249.1 203.7,243.7C220.2,238.1 238.5,240.3 253,250Z"/>
|
||||
|
||||
</vector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_banner_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_banner_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 166 KiB |
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:keep="@raw/aboutlibraries" />
|
||||
@@ -1,2 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<color name="ic_banner_background">#21272A</color>
|
||||
</resources>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -553,4 +553,6 @@
|
||||
|
||||
<string name="local_network_permission_denied">Local network access denied. Some features may not work properly</string>
|
||||
<string name="stop_while_captive_portal">Stop tunnel while captive portal is present</string>
|
||||
<string name="seamless_roaming">Seamless roaming</string>
|
||||
<string name="seamless_roaming_description">Bind tunnels to the active network to improve reliability on network handoffs</string>
|
||||
</resources>
|
||||
|
||||
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 871 B |
|
Before Width: | Height: | Size: 762 B |
|
Before Width: | Height: | Size: 735 B After Width: | Height: | Size: 629 B |
|
Before Width: | Height: | Size: 576 B |
|
Before Width: | Height: | Size: 503 B |
|
Before Width: | Height: | Size: 466 B After Width: | Height: | Size: 423 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 995 B After Width: | Height: | Size: 839 B |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="512" android:viewportWidth="512" android:width="24dp">
|
||||
|
||||
<path android:fillColor="#FEFEFE" android:pathData="M387.85,105.96C390.08,105.99 392.31,106.01 394.54,106.02C425.06,106.26 425.06,106.26 435.06,115.31C440.67,121.68 443.41,128.87 443.28,137.35C442.12,148.45 436.55,159.27 432.19,169.44C431.49,171.08 430.79,172.73 430.09,174.37C428.56,177.97 427.03,181.57 425.49,185.17C422.88,191.28 420.28,197.41 417.68,203.53C353.09,355.51 353.09,355.51 338.06,385.06C337.46,386.28 337.46,386.28 336.85,387.51C331.72,397.31 324.43,403.52 314,407C305.35,407.63 297.48,407.88 290,403C289.35,402.61 288.7,402.21 288.03,401.81C267.22,386.65 255.93,343.57 246.12,320.63C243.84,315.3 241.56,309.97 239.27,304.64C235.16,295.07 231.06,285.5 226.96,275.93C216,250.31 216,250.31 205,224.7C195.27,202.04 185.54,179.37 176.31,156.5C176,155.72 175.68,154.95 175.36,154.15C170.97,143.16 169.24,134.79 173.75,123.58C177.5,115.83 183.04,112.05 191,109C199.93,106.88 206.75,108.16 215,112C223.45,117.42 227.57,127.73 231.75,136.56C232.2,137.51 232.65,138.46 233.12,139.44C244.41,163.45 254.74,187.93 265.15,212.33C268.22,219.52 271.33,226.7 274.44,233.88C274.98,235.13 275.53,236.39 276.07,237.65C278.53,243.32 281,248.99 283.47,254.66C289.07,267.5 294.62,280.36 300,293.3C300.54,294.59 301.08,295.88 301.62,297.17C302.36,298.95 303.1,300.73 303.83,302.52C304.25,303.53 304.67,304.54 305.1,305.59C306,308 306,308 306,310C306.66,310 307.32,310 308,310C308.17,309.38 308.35,308.77 308.52,308.13C310.24,302.19 312.3,296.56 314.77,290.89C315.13,290.05 315.49,289.22 315.86,288.36C317.03,285.65 318.2,282.95 319.38,280.25C320.19,278.35 321.01,276.46 321.83,274.56C323.85,269.88 325.88,265.2 327.9,260.53C329.44,256.98 330.98,253.42 332.52,249.87C334.58,245.1 336.65,240.34 338.71,235.57C341.15,229.96 343.58,224.35 346,218.74C346.35,217.95 346.69,217.15 347.05,216.33C348.78,212.33 350.51,208.33 352.23,204.32C355.22,197.41 358.21,190.5 361.24,183.61C361.71,182.53 362.19,181.45 362.67,180.34C363.53,178.38 364.39,176.42 365.26,174.47C365.63,173.63 366,172.79 366.38,171.93C366.85,170.85 366.85,170.85 367.34,169.75C368.12,168 368.12,168 368,166C366.76,165.99 366.76,165.99 365.49,165.98C357.63,165.9 349.78,165.81 341.92,165.71C337.88,165.66 333.84,165.62 329.81,165.58C325.9,165.54 322,165.49 318.1,165.44C316.62,165.42 315.13,165.41 313.65,165.4C300.1,165.29 288.99,164.82 278.75,155.19C273.15,149.32 271.03,143.02 271,135C271.65,126.87 275.22,120.31 281.34,114.95C296.39,103.76 318.78,106.75 336.69,106.44C338.39,106.41 338.39,106.41 340.12,106.37C373.39,105.77 373.39,105.77 387.85,105.96Z"/>
|
||||
|
||||
<path android:fillColor="#FEFEFE" android:pathData="M114,113C120.76,117.8 124.22,123.2 127.69,130.63C128.19,131.68 128.7,132.73 129.22,133.81C130.84,137.2 132.43,140.59 134,144C134.5,145.08 135,146.16 135.52,147.28C144.69,167.16 153.19,187.35 161.79,207.48C165.04,215.1 168.33,222.71 171.63,230.31C172.14,231.49 172.65,232.67 173.18,233.89C175.39,239 177.61,244.1 179.83,249.21C189.5,271.38 198.88,293.66 208.11,316.01C211.04,323.11 214.01,330.18 217,337.25C217.43,338.26 217.86,339.28 218.3,340.32C220.39,345.24 222.5,350.16 224.63,355.07C225.05,356.04 225.47,357.01 225.91,358C226.69,359.81 227.48,361.62 228.27,363.42C232.37,372.86 233.7,381.34 229.94,391.19C226.56,398.54 222.59,403.08 215.19,406.44C207.09,409.27 200.15,408.78 192.38,405.23C182.8,400.26 178.73,393.43 174,384C173.52,383.06 173.05,382.12 172.56,381.15C162.64,361.39 153.83,341.17 145.1,320.88C143.13,316.31 141.16,311.74 139.17,307.18C130.23,286.6 121.43,265.97 112.65,245.32C107.35,232.86 102.04,220.41 96.69,207.98C90.69,194.05 84.73,180.11 78.92,166.1C78.32,164.64 77.71,163.18 77.1,161.72C67.42,138.54 67.42,138.54 71.56,126.94C74.89,119.43 79.6,114.1 87.11,110.69C96.25,107.35 105.69,107.97 114,113Z"/>
|
||||
|
||||
<path android:fillColor="#FEFEFE" android:pathData="M324,178C331.04,182.91 336.31,189.42 338,198C338.98,206.07 336.97,213.6 332.42,220.34C327.99,225.71 322.98,229.65 316,231C306.84,231.81 299.67,231.33 292,226C289.13,223.5 289.13,223.5 287,221C286.28,220.2 285.56,219.39 284.81,218.56C279.84,211.53 280.29,203.26 281,195C281.85,191.88 283.09,189.59 285,187C285.42,186.39 285.85,185.78 286.29,185.14C295.31,173.35 311.34,170.15 324,178Z"/>
|
||||
|
||||
</vector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_banner_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_banner_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@mipmap/ic_launcher_background"/>
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@mipmap/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 942 B |
|
Before Width: | Height: | Size: 1000 B After Width: | Height: | Size: 1012 B |
|
After Width: | Height: | Size: 1012 B |
|
Before Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_banner_background">#52357B</color>
|
||||
</resources>
|
||||
@@ -9,6 +9,7 @@ plugins {
|
||||
alias(libs.plugins.ktfmt)
|
||||
alias(libs.plugins.licensee) apply false
|
||||
alias(libs.plugins.jetbrains.kotlin.jvm) apply false
|
||||
alias(libs.plugins.aboutlibraries) apply false
|
||||
}
|
||||
|
||||
subprojects {
|
||||
|
||||
@@ -49,6 +49,7 @@ relinker = "1.4.5"
|
||||
libsu = "6.0.0"
|
||||
jetbrainsKotlinJvm = "2.4.0"
|
||||
sonner = "0.3.9"
|
||||
aboutlibraries = "15.0.3"
|
||||
|
||||
[bundles]
|
||||
# Core AndroidX foundations
|
||||
@@ -201,6 +202,8 @@ shizuku-provider = { module = "dev.rikka.shizuku:provider", version.ref = "shizu
|
||||
relinker = { module = "com.getkeepsafe.relinker:relinker", version.ref = "relinker" }
|
||||
libsu = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu" }
|
||||
|
||||
aboutlibraries-compose = { module = "com.mikepenz:aboutlibraries-compose-m3", version.ref = "aboutlibraries" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
|
||||
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
||||
@@ -210,4 +213,5 @@ compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "
|
||||
grgit = { id = "org.ajoberstar.grgit.service", version.ref = "gradlePlugins-grgit" }
|
||||
ktfmt = { id = "com.ncorti.ktfmt.gradle", version.ref = "ktfmt" }
|
||||
licensee = { id = "app.cash.licensee", version.ref = "licensee" }
|
||||
jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrainsKotlinJvm" }
|
||||
jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrainsKotlinJvm" }
|
||||
aboutlibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutlibraries" }
|
||||
|
||||
@@ -12,6 +12,8 @@ internal object ProxyBackend {
|
||||
|
||||
external fun awgTurnProxyTunnelOff(handle: Int)
|
||||
|
||||
external fun awgTriggerProxyBindUpdate(handle: Int)
|
||||
|
||||
external fun awgGetProxyConfig(handle: Int): String
|
||||
|
||||
fun setSocketProtector(sp: SocketProtector?) {
|
||||
|
||||
@@ -28,6 +28,10 @@ interface Backend {
|
||||
|
||||
suspend fun stopAllActiveTunnels(): Result<Unit>
|
||||
|
||||
suspend fun setSeamlessRoaming(enabled: Boolean): Result<Unit>
|
||||
|
||||
val isSeamlessRoamingEnabled: Boolean
|
||||
|
||||
val status: Flow<BackendStatus>
|
||||
|
||||
val events: Flow<TunnelEvent>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.zaneschepke.tunnel.backend
|
||||
|
||||
import com.zaneschepke.networkmonitor.ActiveNetwork
|
||||
import com.zaneschepke.networkmonitor.StableNetworkEngine
|
||||
import com.zaneschepke.tunnel.ApplicationProvider
|
||||
import com.zaneschepke.tunnel.StatusCallback
|
||||
@@ -30,6 +31,7 @@ import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.awaitCancellation
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -41,7 +43,9 @@ import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
@@ -68,6 +72,10 @@ class TunnelBackend(
|
||||
private val _status = MutableStateFlow(BackendStatus())
|
||||
override val status: Flow<BackendStatus> = _status.asStateFlow()
|
||||
|
||||
private val _isSeamlessRoamingEnabled = MutableStateFlow(false)
|
||||
override val isSeamlessRoamingEnabled: Boolean
|
||||
get() = _isSeamlessRoamingEnabled.value
|
||||
|
||||
private val _events = MutableSharedFlow<TunnelEvent>(extraBufferCapacity = 32)
|
||||
override val events = _events.asSharedFlow()
|
||||
|
||||
@@ -78,6 +86,7 @@ class TunnelBackend(
|
||||
private val byTunnelId = ConcurrentHashMap<Int, Int>()
|
||||
private val peerUpdateMutexes = ConcurrentHashMap<Int, Mutex>()
|
||||
private val pendingResolutionJobs = ConcurrentHashMap<Int, Job>()
|
||||
private var seamlessRoamingJob: Job? = null
|
||||
|
||||
private val endpointResolver =
|
||||
EndpointResolver(
|
||||
@@ -146,6 +155,55 @@ class TunnelBackend(
|
||||
.onFailure { cleanup(tunnel.id) }
|
||||
}
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
override suspend fun setSeamlessRoaming(enabled: Boolean): Result<Unit> = runCatching {
|
||||
if (_isSeamlessRoamingEnabled.value == enabled) return@runCatching
|
||||
|
||||
_isSeamlessRoamingEnabled.value = enabled
|
||||
seamlessRoamingJob?.cancel()
|
||||
seamlessRoamingJob = null
|
||||
|
||||
if (enabled) {
|
||||
seamlessRoamingJob = scope.launch {
|
||||
stableNetworkEngine.stableState
|
||||
.distinctUntilChangedBy { it?.key }
|
||||
.map { it?.state?.activeNetwork }
|
||||
.debounce(300.milliseconds)
|
||||
.collect { network ->
|
||||
if (
|
||||
network != null &&
|
||||
network !is ActiveNetwork.Disconnected &&
|
||||
_status.value.activeTunnels.isNotEmpty()
|
||||
) {
|
||||
Timber.d(
|
||||
"Seamless Roaming: Network changed to ${network.key()}, updating bind on active tunnels"
|
||||
)
|
||||
updateBindForActiveTunnels()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Timber.d("Seamless Roaming disabled, rebinding to remove network bind")
|
||||
updateBindForActiveTunnels()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateBindForActiveTunnels() {
|
||||
// Take a snapshot to avoid race
|
||||
val currentTunnels = _status.value.activeTunnels.toMap()
|
||||
|
||||
currentTunnels.forEach { (id, activeTunnel) ->
|
||||
val handle = byTunnelId[id] ?: return@forEach
|
||||
val mode = activeTunnel.mode ?: return@forEach
|
||||
|
||||
try {
|
||||
engine.updateBind(handle, mode)
|
||||
} catch (t: Throwable) {
|
||||
Timber.w(t, "Failed to update bind for tunnel $id during bulk update")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startTunnelBootstrapJob(tunnel: Tunnel, mode: BackendMode) = scope.launch {
|
||||
try {
|
||||
updateTunnelBootstrapState(tunnel.id, BootstrapState.ResolvingDns)
|
||||
|
||||
@@ -15,4 +15,6 @@ internal interface TunnelEngine {
|
||||
suspend fun updatePeers(handle: Int, mode: BackendMode, peers: List<PeerSection>)
|
||||
|
||||
suspend fun getActiveConfig(handle: Int, mode: BackendMode): ActiveConfig?
|
||||
|
||||
suspend fun updateBind(handle: Int, mode: BackendMode)
|
||||
}
|
||||
|
||||
@@ -106,6 +106,14 @@ internal class WireGuardTunnelEngine(private val serviceHolder: ServiceHolder) :
|
||||
return rawConfig?.let { ActiveConfig.parseFromIpc(it) }
|
||||
}
|
||||
|
||||
override suspend fun updateBind(handle: Int, mode: BackendMode) {
|
||||
when (mode) {
|
||||
is BackendMode.Proxy.KillSwitchPrimary,
|
||||
is BackendMode.Proxy.Standard -> ProxyBackend.awgTriggerProxyBindUpdate(handle)
|
||||
is BackendMode.Vpn -> VpnBackend.awgTriggerBindUpdate(handle)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun stop(handle: Int, mode: BackendMode) {
|
||||
when (mode) {
|
||||
is BackendMode.Proxy.Standard -> stopProxyTunnel(handle)
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.zaneschepke.tunnel.backend.dns
|
||||
import android.content.Context
|
||||
import android.net.DnsResolver
|
||||
import android.net.Network
|
||||
import android.net.TrafficStats
|
||||
import android.os.Build
|
||||
import android.os.CancellationSignal
|
||||
import androidx.annotation.RequiresApi
|
||||
@@ -66,21 +67,40 @@ internal class AndroidNetworkResolver(private val network: Network) : PeerResolv
|
||||
val signal = CancellationSignal()
|
||||
continuation.invokeOnCancellation { signal.cancel() }
|
||||
|
||||
dnsResolver.query(
|
||||
network,
|
||||
host,
|
||||
DnsResolver.FLAG_EMPTY,
|
||||
Executor { it.run() },
|
||||
signal,
|
||||
object : DnsResolver.Callback<List<InetAddress>> {
|
||||
override fun onAnswer(answer: List<InetAddress>, rcode: Int) {
|
||||
continuation.resume(answer)
|
||||
}
|
||||
val oldTag = TrafficStats.getThreadStatsTag()
|
||||
TrafficStats.setThreadStatsTag(DNS_TRAFFIC_TAG)
|
||||
|
||||
override fun onError(error: DnsResolver.DnsException) {
|
||||
continuation.resumeWithException(error)
|
||||
}
|
||||
},
|
||||
)
|
||||
try {
|
||||
dnsResolver.query(
|
||||
network,
|
||||
host,
|
||||
DnsResolver.FLAG_EMPTY,
|
||||
Executor { command ->
|
||||
val executorOldTag = TrafficStats.getThreadStatsTag()
|
||||
TrafficStats.setThreadStatsTag(DNS_TRAFFIC_TAG)
|
||||
try {
|
||||
command.run()
|
||||
} finally {
|
||||
TrafficStats.setThreadStatsTag(executorOldTag)
|
||||
}
|
||||
},
|
||||
signal,
|
||||
object : DnsResolver.Callback<List<InetAddress>> {
|
||||
override fun onAnswer(answer: List<InetAddress>, rcode: Int) {
|
||||
continuation.resume(answer)
|
||||
}
|
||||
|
||||
override fun onError(error: DnsResolver.DnsException) {
|
||||
continuation.resumeWithException(error)
|
||||
}
|
||||
},
|
||||
)
|
||||
} finally {
|
||||
TrafficStats.setThreadStatsTag(oldTag)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DNS_TRAFFIC_TAG = 1000
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ internal class ServiceHolder(val context: Context) {
|
||||
|
||||
fun set(service: TunnelService) {
|
||||
_tunnelService.value = service
|
||||
ProxyBackend.setSocketProtector(service)
|
||||
}
|
||||
|
||||
fun clearVpnService() {
|
||||
@@ -40,6 +41,7 @@ internal class ServiceHolder(val context: Context) {
|
||||
}
|
||||
|
||||
fun clearTunnelService() {
|
||||
ProxyBackend.setSocketProtector(null)
|
||||
_tunnelService.value = null
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,13 @@ package com.zaneschepke.tunnel.service
|
||||
import android.app.NotificationManager
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.os.ParcelFileDescriptor
|
||||
import androidx.core.app.ServiceCompat
|
||||
import androidx.lifecycle.LifecycleService
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.zaneschepke.networkmonitor.StableNetworkEngine
|
||||
import com.zaneschepke.tunnel.backend.Backend
|
||||
import com.zaneschepke.tunnel.backend.SocketProtector
|
||||
import com.zaneschepke.tunnel.service.ServiceHolder.Companion.alwaysOnCallback
|
||||
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
@@ -20,9 +23,11 @@ import kotlinx.coroutines.launch
|
||||
import org.koin.java.KoinJavaComponent.inject
|
||||
import timber.log.Timber
|
||||
|
||||
class TunnelService : LifecycleService() {
|
||||
class TunnelService : LifecycleService(), SocketProtector {
|
||||
|
||||
private val backend: Backend by inject(Backend::class.java)
|
||||
|
||||
private val stableNetworkEngine: StableNetworkEngine by inject(StableNetworkEngine::class.java)
|
||||
private val serviceHolder: ServiceHolder by inject(ServiceHolder::class.java)
|
||||
private val shutdownScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
|
||||
@@ -108,4 +113,23 @@ class TunnelService : LifecycleService() {
|
||||
ServiceHolder.SPECIAL_USE_SERVICE_TYPE_ID,
|
||||
)
|
||||
}
|
||||
|
||||
// TODO We'll reuse this for now for doing our network binding
|
||||
override fun bypass(fd: Int): Int {
|
||||
if (backend.isSeamlessRoamingEnabled) {
|
||||
stableNetworkEngine.stableState.value?.state?.activeNetwork?.network?.let { net ->
|
||||
val pfd = ParcelFileDescriptor.adoptFd(fd)
|
||||
return try {
|
||||
net.bindSocket(pfd.fileDescriptor)
|
||||
1
|
||||
} catch (e: Exception) {
|
||||
Timber.w(e, "bindSocket failed for fd=$fd")
|
||||
0
|
||||
} finally {
|
||||
pfd.detachFd()
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.os.ParcelFileDescriptor
|
||||
import android.system.OsConstants
|
||||
import com.zaneschepke.hevtunnel.HevTunnelConfig
|
||||
import com.zaneschepke.hevtunnel.TProxyService
|
||||
import com.zaneschepke.networkmonitor.StableNetworkEngine
|
||||
import com.zaneschepke.tunnel.Tunnel
|
||||
import com.zaneschepke.tunnel.backend.Backend
|
||||
import com.zaneschepke.tunnel.backend.KillSwitch
|
||||
@@ -36,6 +37,7 @@ import timber.log.Timber
|
||||
class VpnService : android.net.VpnService(), KillSwitch, SocketProtector {
|
||||
|
||||
private val backend: Backend by inject(Backend::class.java)
|
||||
private val stableNetworkEngine: StableNetworkEngine by inject(StableNetworkEngine::class.java)
|
||||
private val serviceHolder: ServiceHolder by inject(ServiceHolder::class.java)
|
||||
|
||||
private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
@@ -336,16 +338,25 @@ class VpnService : android.net.VpnService(), KillSwitch, SocketProtector {
|
||||
}
|
||||
|
||||
override fun bypass(fd: Int): Int {
|
||||
Timber.d("Bypassing VPN fd: $fd")
|
||||
val bypassed =
|
||||
try {
|
||||
if (protect(fd)) 1 else 0
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Failed to protect VPN fd")
|
||||
0
|
||||
return try {
|
||||
if (backend.isSeamlessRoamingEnabled) {
|
||||
stableNetworkEngine.stableState.value?.state?.activeNetwork?.network?.let { net ->
|
||||
val pfd = ParcelFileDescriptor.adoptFd(fd)
|
||||
try {
|
||||
net.bindSocket(pfd.fileDescriptor)
|
||||
Timber.d("Bound socket $fd to network $net")
|
||||
} catch (e: Exception) {
|
||||
Timber.w(e, "bindSocket failed for fd=$fd")
|
||||
} finally {
|
||||
pfd.detachFd()
|
||||
}
|
||||
}
|
||||
}
|
||||
Timber.d("Socket protected result: $fd")
|
||||
return bypassed
|
||||
if (protect(fd)) 1 else 0
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Failed to protect/bypass fd=$fd")
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
interface AlwaysOnCallback {
|
||||
|
||||
@@ -71,12 +71,7 @@ func awgStartProxy(interfaceName string, config string, uapiPath string, bypass
|
||||
return -1
|
||||
}
|
||||
|
||||
var bind conn.Bind
|
||||
if bypass == 1 {
|
||||
bind = conn.NewStdNetBindWithControl(shared.ProtectControlFunc)
|
||||
} else {
|
||||
bind = conn.NewStdNetBind()
|
||||
}
|
||||
bind := conn.NewStdNetBindWithControl(shared.ProtectControlFunc)
|
||||
|
||||
statusCB := func(code device.StatusCode) {
|
||||
key := handle
|
||||
|
||||