Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c78e9fcbd | |||
| 8bdeff515e | |||
| fa89715ede | |||
| 4a94905893 | |||
| cf184f2042 | |||
| e0ddb8730d | |||
| c8c041b872 | |||
| 7c8adb380b | |||
| 614f97fd14 | |||
| fbd470f5d2 | |||
| 5f89b2ed31 | |||
| 9503a3284b | |||
| 68c1a19bd3 | |||
| f3bb6667c3 | |||
| 244a990c37 | |||
| cbf07600b4 | |||
| ec8d90d13d | |||
| 85acca8604 | |||
| 0a9773d202 | |||
| 3cb4480a65 | |||
| a7f3255a76 | |||
| 7d7b99f448 | |||
| 74e9e462bb | |||
| 619e3c1cde | |||
| 77f8a8215b | |||
| 8772036dd7 | |||
| 63625ccbd7 | |||
| 9ac7ae77b3 | |||
| e062fbb34d | |||
| 16d5586433 | |||
| 48a3ad64f4 | |||
| e5796d641d |
@@ -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,513 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 31,
|
||||
"identityHash": "1dee3799f1c6526c48723fd2fee58d11",
|
||||
"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)",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"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, '1dee3799f1c6526c48723fd2fee58d11')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,6 @@
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.App.Start"
|
||||
tools:targetApi="tiramisu">
|
||||
@@ -74,6 +73,13 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="wg" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<action android:name="android.intent.action.SHOW_APP_INFO" />
|
||||
@@ -150,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
|
||||
@@ -171,7 +177,7 @@
|
||||
<service
|
||||
android:name=".service.tile.TunnelControlTile"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_notification"
|
||||
android:icon="@drawable/qs_logo"
|
||||
android:label="@string/tunnel_control"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
<meta-data
|
||||
@@ -188,7 +194,7 @@
|
||||
<service
|
||||
android:name=".service.tile.AutoTunnelControlTile"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_notification"
|
||||
android:icon="@drawable/qs_logo"
|
||||
android:label="@string/auto_tunnel"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
<meta-data
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package com.zaneschepke.wireguardautotunnel
|
||||
|
||||
import ProxySettingsScreen
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.net.VpnService
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.view.WindowManager
|
||||
import androidx.activity.SystemBarStyle
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
@@ -31,15 +33,10 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.CheckCircleOutline
|
||||
import androidx.compose.material.icons.outlined.Error
|
||||
import androidx.compose.material.icons.outlined.ErrorOutline
|
||||
import androidx.compose.material.icons.outlined.FavoriteBorder
|
||||
import androidx.compose.material.icons.outlined.Info
|
||||
import androidx.compose.material.icons.outlined.Warning
|
||||
import androidx.compose.material.icons.outlined.WarningAmber
|
||||
import androidx.compose.material.icons.rounded.Error
|
||||
import androidx.compose.material.icons.rounded.Info
|
||||
import androidx.compose.material.icons.rounded.Warning
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -55,6 +52,7 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -66,6 +64,7 @@ import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
|
||||
@@ -87,6 +86,8 @@ import com.zaneschepke.wireguardautotunnel.domain.sideeffect.GlobalSideEffect
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.banner.AppAlertBanner
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.LocalNetworkPermissionDialog
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.VpnDeniedDialog
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.Route
|
||||
import com.zaneschepke.wireguardautotunnel.ui.navigation.SecureRoute
|
||||
@@ -136,6 +137,7 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.installApk
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.restartApp
|
||||
import com.zaneschepke.wireguardautotunnel.util.permission.LocalNetworkPermissionHelper
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.ConfigEditViewModel
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.SharedAppViewModel
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.SplitTunnelViewModel
|
||||
@@ -181,7 +183,8 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
roomBackup = RoomBackup(this).database(appDatabase).enableLogDebug(true).maxFileCount(5)
|
||||
|
||||
handleIncomingIntent(intent)
|
||||
handleConfigFileIntent(intent)
|
||||
handleWgDeepLinkIntent(intent)
|
||||
|
||||
installSplashScreen().apply {
|
||||
setKeepOnScreenCondition { !viewModel.container.stateFlow.value.isAppLoaded }
|
||||
@@ -205,6 +208,48 @@ class MainActivity : AppCompatActivity() {
|
||||
var requestingTunnelMode by remember {
|
||||
mutableStateOf<Pair<TunnelMode?, TunnelConfig?>>(Pair(null, null))
|
||||
}
|
||||
var showLocalNetworkRationale by remember { mutableStateOf(false) }
|
||||
var hasPromptedLocalNetwork by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
val localNetworkPermissionLauncher =
|
||||
rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.RequestPermission()
|
||||
) { isGranted ->
|
||||
if (!isGranted) {
|
||||
val canAskAgain =
|
||||
ActivityCompat.shouldShowRequestPermissionRationale(
|
||||
this,
|
||||
Manifest.permission.ACCESS_LOCAL_NETWORK,
|
||||
)
|
||||
|
||||
if (!canAskAgain) {
|
||||
val intent =
|
||||
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||
data = Uri.fromParts("package", packageName, null)
|
||||
}
|
||||
startActivity(intent)
|
||||
} else {
|
||||
toaster.show(
|
||||
message =
|
||||
context.getString(R.string.local_network_permission_denied),
|
||||
type = ToastType.Warning,
|
||||
duration = 6000.milliseconds,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(uiState.isAppLoaded) {
|
||||
if (
|
||||
uiState.isAppLoaded &&
|
||||
!hasPromptedLocalNetwork &&
|
||||
LocalNetworkPermissionHelper.shouldRequestPermission() &&
|
||||
!LocalNetworkPermissionHelper.isPermissionGranted(context)
|
||||
) {
|
||||
hasPromptedLocalNetwork = true
|
||||
showLocalNetworkRationale = true
|
||||
}
|
||||
}
|
||||
|
||||
val startingStack = buildList {
|
||||
add(Route.Tunnels)
|
||||
@@ -295,6 +340,38 @@ class MainActivity : AppCompatActivity() {
|
||||
},
|
||||
)
|
||||
|
||||
if (showLocalNetworkRationale) {
|
||||
LocalNetworkPermissionDialog(
|
||||
onDismiss = {
|
||||
showLocalNetworkRationale = false
|
||||
toaster.show(
|
||||
message =
|
||||
context.getString(R.string.local_network_permission_denied),
|
||||
type = ToastType.Warning,
|
||||
duration = 6000.milliseconds,
|
||||
)
|
||||
},
|
||||
onAttest = {
|
||||
showLocalNetworkRationale = false
|
||||
|
||||
localNetworkPermissionLauncher.launch(
|
||||
Manifest.permission.ACCESS_LOCAL_NETWORK
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
uiState.pendingWgImportUrl?.let { url ->
|
||||
val host = Uri.parse(url).host ?: url
|
||||
InfoDialog(
|
||||
onDismiss = { viewModel.dismissWgImport() },
|
||||
onAttest = { viewModel.importFromUrl(url) },
|
||||
title = stringResource(R.string.add_from_url),
|
||||
body = { Text(stringResource(R.string.wg_url_confirm_message, host)) },
|
||||
confirmText = stringResource(R.string.okay),
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (uiState.shouldShowDonationSnackbar && !uiState.alreadyDonated) {
|
||||
viewModel.setShouldShowDonationSnackbar(false)
|
||||
@@ -597,6 +674,21 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleWgDeepLinkIntent(intent: Intent) {
|
||||
if (intent.action == Intent.ACTION_VIEW) {
|
||||
val uri = intent.data ?: return
|
||||
if (uri.scheme == "wg") {
|
||||
val httpsUrl = uri.toString().replaceFirst("wg://", "https://")
|
||||
viewModel.promptWgImport(httpsUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
networkMonitor.checkPermissionsAndUpdateState()
|
||||
}
|
||||
|
||||
fun performBackup(encrypt: Boolean = false, password: String? = null) {
|
||||
roomBackup
|
||||
.backupLocation(RoomBackup.BACKUP_FILE_LOCATION_CUSTOM_DIALOG)
|
||||
@@ -673,18 +765,14 @@ class MainActivity : AppCompatActivity() {
|
||||
.restore()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
networkMonitor.checkPermissionsAndUpdateState()
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
setIntent(intent)
|
||||
handleIncomingIntent(intent)
|
||||
handleConfigFileIntent(intent)
|
||||
handleWgDeepLinkIntent(intent)
|
||||
}
|
||||
|
||||
private fun handleIncomingIntent(intent: Intent?) {
|
||||
private fun handleConfigFileIntent(intent: Intent?) {
|
||||
intent ?: return
|
||||
when (intent.action) {
|
||||
Intent.ACTION_VIEW,
|
||||
|
||||
@@ -51,6 +51,13 @@ class WireGuardAutoTunnel : Application(), KoinComponent {
|
||||
|
||||
private val backend: Backend by inject()
|
||||
|
||||
private val alwaysOnCallback =
|
||||
object : VpnService.AlwaysOnCallback {
|
||||
override fun alwaysOnTriggered() {
|
||||
applicationScope.launch { tunnelCoordinator.startDefault() }
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(KoinViewModelScopeApi::class)
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
@@ -86,13 +93,7 @@ class WireGuardAutoTunnel : Application(), KoinComponent {
|
||||
Timber.plant(ReleaseTree())
|
||||
}
|
||||
|
||||
backend.setAlwaysOnCallback(
|
||||
object : VpnService.AlwaysOnCallback {
|
||||
override fun alwaysOnTriggered() {
|
||||
applicationScope.launch { tunnelCoordinator.startDefault() }
|
||||
}
|
||||
}
|
||||
)
|
||||
backend.setAlwaysOnCallback(alwaysOnCallback)
|
||||
|
||||
val dispatcher = get<TunnelEventDispatcher>()
|
||||
val coordinator = get<TunnelCoordinator>()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,12 @@ import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelMode
|
||||
import com.zaneschepke.wireguardautotunnel.domain.events.TunnelActionEvent
|
||||
import com.zaneschepke.wireguardautotunnel.domain.model.DnsSettings
|
||||
import com.zaneschepke.wireguardautotunnel.domain.model.GeneralSettings
|
||||
import com.zaneschepke.wireguardautotunnel.domain.model.LockdownSettings
|
||||
import com.zaneschepke.wireguardautotunnel.domain.model.MonitoringSettings
|
||||
import com.zaneschepke.wireguardautotunnel.domain.model.ProxySettings
|
||||
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.GeneralSettingRepository
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.LockdownSettingsRepository
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.MonitoringSettingsRepository
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.ProxySettingsRepository
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
|
||||
@@ -44,6 +46,7 @@ class TunnelCoordinator(
|
||||
dnsSettingsRepository: RoomDnsSettingsRepository,
|
||||
monitoringSettingsRepository: MonitoringSettingsRepository,
|
||||
proxyRepository: ProxySettingsRepository,
|
||||
lockdownModeRepository: LockdownSettingsRepository,
|
||||
scope: CoroutineScope,
|
||||
) {
|
||||
|
||||
@@ -66,6 +69,7 @@ class TunnelCoordinator(
|
||||
val dns: DnsSettings,
|
||||
val monitoring: MonitoringSettings,
|
||||
val proxy: ProxySettings,
|
||||
val lockdown: LockdownSettings,
|
||||
)
|
||||
|
||||
private val runtimeSettingsSnapshot =
|
||||
@@ -74,12 +78,14 @@ class TunnelCoordinator(
|
||||
dnsSettingsRepository.flow,
|
||||
monitoringSettingsRepository.flow,
|
||||
proxyRepository.flow,
|
||||
) { general, dns, monitoring, proxy ->
|
||||
lockdownModeRepository.flow,
|
||||
) { general, dns, monitoring, proxy, lockdown ->
|
||||
RuntimeSettingsSnapshot(
|
||||
general = general,
|
||||
dns = dns,
|
||||
monitoring = monitoring,
|
||||
proxy = proxy,
|
||||
lockdown = lockdown,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -117,7 +123,7 @@ class TunnelCoordinator(
|
||||
|
||||
// enforce single tunnel, for now
|
||||
if (backendStatus.value.activeTunnels.isNotEmpty()) {
|
||||
stopActiveTunnelsInternal()
|
||||
stopActiveTunnelsInternal(source)
|
||||
}
|
||||
|
||||
startTunnelInternal(config, source)
|
||||
@@ -131,7 +137,13 @@ class TunnelCoordinator(
|
||||
stopTunnelInternal(id, source)
|
||||
}
|
||||
|
||||
suspend fun stopActiveTunnels() = tunnelMutex.withLock { stopActiveTunnelsInternal() }
|
||||
suspend fun stopActiveTunnels(source: TunnelActionSource = TunnelActionSource.USER) =
|
||||
tunnelMutex.withLock {
|
||||
if (source == TunnelActionSource.USER) {
|
||||
_userOverrideFlow.tryEmit(Unit)
|
||||
}
|
||||
stopActiveTunnelsInternal(source)
|
||||
}
|
||||
|
||||
private suspend fun startTunnelInternal(
|
||||
tunnelConfig: TunnelConfig,
|
||||
@@ -143,6 +155,7 @@ class TunnelCoordinator(
|
||||
val dnsSettings = snapshot.dns
|
||||
val proxySettings = snapshot.proxy
|
||||
val monitoringSettings = snapshot.monitoring
|
||||
val lockdownSettings = snapshot.lockdown
|
||||
|
||||
val config = tunnelConfig.getConfig()
|
||||
val policy =
|
||||
@@ -178,8 +191,10 @@ class TunnelCoordinator(
|
||||
}
|
||||
|
||||
TunnelMode.LOCK_DOWN -> {
|
||||
|
||||
BackendMode.Proxy.KillSwitchPrimary(runConfig)
|
||||
BackendMode.Proxy.KillSwitchPrimary(
|
||||
runConfig,
|
||||
lockdownSettings.toKillSwitchConfig(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +233,7 @@ class TunnelCoordinator(
|
||||
_actions.emit(TunnelActionEvent.Stopped(tunnelId = id, source = source))
|
||||
}
|
||||
|
||||
stopActiveTunnelsInternal()
|
||||
stopActiveTunnelsInternal(source)
|
||||
return@withLock
|
||||
}
|
||||
|
||||
@@ -243,7 +258,15 @@ class TunnelCoordinator(
|
||||
.onFailure { _errors.emit(TunnelErrorEvent.from(it, id)) }
|
||||
}
|
||||
|
||||
private suspend fun stopActiveTunnelsInternal() {
|
||||
private suspend fun stopActiveTunnelsInternal(
|
||||
source: TunnelActionSource = TunnelActionSource.USER
|
||||
) {
|
||||
val active = tunnelProvider.backendStatus.value.activeTunnels
|
||||
|
||||
active.keys.forEach { id ->
|
||||
_actions.emit(TunnelActionEvent.Stopped(tunnelId = id, source = source))
|
||||
}
|
||||
|
||||
tunnelProvider.stopActiveTunnels()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,8 @@ class ShortcutsActivity : ComponentActivity() {
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
applicationScope.launch {
|
||||
shortcutCoordinator.handle(intent)
|
||||
finish()
|
||||
}
|
||||
finish()
|
||||
|
||||
applicationScope.launch { shortcutCoordinator.handle(intent) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,11 @@ import com.zaneschepke.tunnel.Tunnel
|
||||
import com.zaneschepke.tunnel.backend.Backend
|
||||
import com.zaneschepke.tunnel.model.BackendMode
|
||||
import com.zaneschepke.tunnel.state.BackendStatus
|
||||
import com.zaneschepke.wireguardautotunnel.domain.events.BackendCoreException
|
||||
import com.zaneschepke.wireguardautotunnel.domain.events.BackendMessage
|
||||
import com.zaneschepke.wireguardautotunnel.domain.model.LockdownSettings
|
||||
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
@@ -53,9 +50,7 @@ class TunnelBackendProvider(
|
||||
return backend.disableKillSwitch()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private val localErrorEvents = MutableSharedFlow<Pair<String?, BackendCoreException>>()
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private val localMessageEvents = MutableSharedFlow<Pair<String?, BackendMessage>>()
|
||||
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 = 30,
|
||||
version = 32,
|
||||
autoMigrations =
|
||||
[
|
||||
AutoMigration(from = 1, to = 2),
|
||||
@@ -63,6 +63,8 @@ import com.zaneschepke.wireguardautotunnel.data.entity.TunnelConfig
|
||||
AutoMigration(from = 26, to = 27, spec = GlobalsMigration::class),
|
||||
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,
|
||||
)
|
||||
|
||||
@@ -18,4 +18,7 @@ interface AutoTunnelSettingsDao {
|
||||
|
||||
@Query("UPDATE auto_tunnel_settings SET is_tunnel_enabled = :enabled")
|
||||
suspend fun updateAutoTunnelEnabled(enabled: Boolean)
|
||||
|
||||
@Query("UPDATE auto_tunnel_settings SET disable_on_captive_portal = :enabled")
|
||||
suspend fun updateDisableOnCaptivePortal(enabled: Boolean)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -27,4 +27,6 @@ data class AutoTunnelSettings(
|
||||
@ColumnInfo(name = "wifi_detection_method", defaultValue = "0")
|
||||
val wifiDetectionMethod: WifiDetectionMethod = WifiDetectionMethod.fromValue(0),
|
||||
@ColumnInfo(name = "start_on_boot", defaultValue = "0") val startOnBoot: Boolean = false,
|
||||
@ColumnInfo(name = "disable_on_captive_portal", defaultValue = "1")
|
||||
val disableTunnelOnCaptivePortal: Boolean = true,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,4 +26,8 @@ class RoomAutoTunnelSettingsRepository(private val autoTunnelSettingsDao: AutoTu
|
||||
override suspend fun updateAutoTunnelEnabled(enabled: Boolean) {
|
||||
autoTunnelSettingsDao.updateAutoTunnelEnabled(enabled)
|
||||
}
|
||||
|
||||
override suspend fun updateDisableOnCaptivePortal(enabled: Boolean) {
|
||||
autoTunnelSettingsDao.updateDisableOnCaptivePortal(enabled)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -27,6 +27,7 @@ val coordinatorModule = module {
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get(named(Scope.APPLICATION)),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,4 +14,5 @@ data class AutoTunnelSettings(
|
||||
val isTunnelOnUnsecureEnabled: Boolean = false,
|
||||
val wifiDetectionMethod: WifiDetectionMethod = WifiDetectionMethod.fromValue(0),
|
||||
val startOnBoot: Boolean = false,
|
||||
val disableTunnelOnCaptivePortal: Boolean = true,
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -11,4 +11,6 @@ interface AutoTunnelSettingsRepository {
|
||||
suspend fun getAutoTunnelSettings(): AutoTunnelSettings
|
||||
|
||||
suspend fun updateAutoTunnelEnabled(enabled: Boolean)
|
||||
|
||||
suspend fun updateDisableOnCaptivePortal(enabled: Boolean)
|
||||
}
|
||||
|
||||
@@ -23,4 +23,6 @@ interface GeneralSettingRepository {
|
||||
suspend fun updateGlobalAmneziaEnabled(enabled: Boolean)
|
||||
|
||||
suspend fun updateScreenRecordingSecurity(enabled: Boolean)
|
||||
|
||||
suspend fun updateSeamlessRoaming(enabled: Boolean)
|
||||
}
|
||||
|
||||
@@ -11,16 +11,20 @@ sealed class ActiveNetwork {
|
||||
|
||||
data object Cellular : ActiveNetwork()
|
||||
|
||||
data class Wifi(val ssid: String, val isSecure: Boolean?) : ActiveNetwork()
|
||||
data class Wifi(
|
||||
val ssid: String,
|
||||
val isSecure: Boolean?,
|
||||
val requiresCaptivePortalLogin: Boolean,
|
||||
) : ActiveNetwork()
|
||||
}
|
||||
|
||||
data class NetworkState(
|
||||
val activeNetwork: ActiveNetwork = ActiveNetwork.Disconnected,
|
||||
val locationServicesEnabled: Boolean = false,
|
||||
val locationPermissionGranted: Boolean = false,
|
||||
) {
|
||||
fun hasInternet(): Boolean = activeNetwork !is ActiveNetwork.Disconnected
|
||||
}
|
||||
// Has a network that can actually transfer data (not suspended)
|
||||
val hasUsableNetwork: Boolean = false,
|
||||
)
|
||||
|
||||
fun ConnectivityState.toDomain(): NetworkState {
|
||||
val domainNetwork: ActiveNetwork =
|
||||
@@ -33,7 +37,11 @@ fun ConnectivityState.toDomain(): NetworkState {
|
||||
null -> null
|
||||
else -> true
|
||||
}
|
||||
ActiveNetwork.Wifi(ssid = network.ssid, isSecure = isSecure)
|
||||
ActiveNetwork.Wifi(
|
||||
ssid = network.ssid,
|
||||
isSecure = isSecure,
|
||||
requiresCaptivePortalLogin(),
|
||||
)
|
||||
}
|
||||
is MonitorActiveNetwork.Cellular -> ActiveNetwork.Cellular
|
||||
is MonitorActiveNetwork.Ethernet -> ActiveNetwork.Ethernet
|
||||
@@ -44,5 +52,6 @@ fun ConnectivityState.toDomain(): NetworkState {
|
||||
activeNetwork = domainNetwork,
|
||||
locationPermissionGranted = this.locationPermissionsGranted,
|
||||
locationServicesEnabled = this.locationServicesEnabled,
|
||||
hasUsableNetwork = hasUsableNetwork(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -28,7 +28,19 @@ class AutoTunnelEngine {
|
||||
|
||||
val activeTunnelIds = backend.activeTunnels.keys.toSet()
|
||||
|
||||
if (!network.hasInternet()) {
|
||||
val isOnCaptivePortalWifi =
|
||||
network.activeNetwork is ActiveNetwork.Wifi &&
|
||||
network.activeNetwork.requiresCaptivePortalLogin
|
||||
|
||||
if (isOnCaptivePortalWifi && settings.disableTunnelOnCaptivePortal) {
|
||||
return if (activeTunnelIds.isNotEmpty()) {
|
||||
Decision.Sync(start = emptySet(), stop = activeTunnelIds)
|
||||
} else {
|
||||
Decision.None
|
||||
}
|
||||
}
|
||||
|
||||
if (!network.hasUsableNetwork) {
|
||||
return if (settings.isStopOnNoInternetEnabled) {
|
||||
Decision.StopDueToNoInternet
|
||||
} else {
|
||||
|
||||
@@ -27,13 +27,14 @@ import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.to
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
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.firstOrNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
@@ -77,13 +78,16 @@ class AutoTunnelService : LifecycleService() {
|
||||
@Volatile private var hasUserOverride = false
|
||||
private var lastNetworkFingerprint: AutoTunnelState.NetworkFingerprint? = null
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
private val autoTunnelStateFlow: Flow<AutoTunnelState> by lazy {
|
||||
val networkFlow = networkEngine.stableState.mapNotNull { it?.state?.toDomain() }
|
||||
|
||||
val settingsFlow = combineSettings()
|
||||
|
||||
val backendFlow =
|
||||
tunnelCoordinator.backendStatus.distinctUntilChangedBy { it.activeTunnels.keys.toSet() }
|
||||
tunnelCoordinator.backendStatus
|
||||
.distinctUntilChanged { old, new -> old.activeTunnels == new.activeTunnels }
|
||||
.debounce(300L.milliseconds)
|
||||
|
||||
combine(networkFlow, settingsFlow, backendFlow) { network, settings, backend ->
|
||||
AutoTunnelState(
|
||||
@@ -188,11 +192,11 @@ class AutoTunnelService : LifecycleService() {
|
||||
reconciliationMutex.withLock {
|
||||
val currentNetworkState = networkEngine.stableState.value?.state?.toDomain()
|
||||
|
||||
val stillNoInternet = currentNetworkState?.hasInternet() == false
|
||||
val stillNoUsableNetwork = currentNetworkState?.hasUsableNetwork == false
|
||||
val stopOnNoInternetEnabled =
|
||||
autoTunnelRepository.flow.firstOrNull()?.isStopOnNoInternetEnabled == true
|
||||
|
||||
if (stillNoInternet && stopOnNoInternetEnabled) {
|
||||
if (stillNoUsableNetwork && stopOnNoInternetEnabled) {
|
||||
val currentActiveIds =
|
||||
tunnelCoordinator.backendStatus.value.activeTunnels.keys
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.common.dialog
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
|
||||
@Composable
|
||||
fun LocalNetworkPermissionDialog(onDismiss: () -> Unit, onAttest: () -> Unit) {
|
||||
InfoDialog(
|
||||
onAttest = onAttest,
|
||||
onDismiss = onDismiss,
|
||||
title = stringResource(R.string.local_network_permission_title),
|
||||
body = {
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.local_network_permission_intro),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.local_network_permission_issues_intro),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Text(
|
||||
text = stringResource(R.string.local_network_permission_feature_tunnels),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.local_network_permission_feature_autotunnel),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.local_network_permission_feature_proxy),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.local_network_permission_recommendation),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.local_network_permission_nearby_devices),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
},
|
||||
confirmText = stringResource(R.string._continue),
|
||||
)
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Filter1
|
||||
import androidx.compose.material.icons.outlined.Map
|
||||
import androidx.compose.material.icons.outlined.PublicOff
|
||||
import androidx.compose.material.icons.outlined.WifiFind
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -191,6 +192,21 @@ fun WifiSettingsScreen(viewModel: AutoTunnelViewModel = koinViewModel()) {
|
||||
)
|
||||
},
|
||||
)
|
||||
SurfaceRow(
|
||||
leading = { Icon(Icons.Outlined.PublicOff, contentDescription = null) },
|
||||
title = stringResource(R.string.stop_while_captive_portal),
|
||||
onClick = {
|
||||
viewModel.setDisabledOnCaptivePortal(
|
||||
!uiState.autoTunnelSettings.disableTunnelOnCaptivePortal
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
ThemedSwitch(
|
||||
checked = uiState.autoTunnelSettings.disableTunnelOnCaptivePortal,
|
||||
onClick = { viewModel.setDisabledOnCaptivePortal(it) },
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
Column {
|
||||
GroupLabel(stringResource(R.string.tunnels), Modifier.padding(horizontal = 16.dp))
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,5 +18,6 @@ data class GlobalAppUiState(
|
||||
val selectedTunnelCount: Int = 0,
|
||||
val alreadyDonated: Boolean = false,
|
||||
val isPinVerified: Boolean = false,
|
||||
val pendingWgImportUrl: String? = null,
|
||||
val isScreenRecordingProtectionEnabled: Boolean = false,
|
||||
)
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.state
|
||||
|
||||
import LicenseFileEntry
|
||||
|
||||
data class LicenseUiState(
|
||||
val isLoading: Boolean = true,
|
||||
val licenses: List<LicenseFileEntry> = emptyList(),
|
||||
)
|
||||
@@ -6,15 +6,11 @@ object Constants {
|
||||
const val BASE_LOG_FILE_NAME = "wg_tunnel_logs"
|
||||
|
||||
const val VPN_SETTINGS_PACKAGE = "android.net.vpn.SETTINGS"
|
||||
const val SYSTEM_EXEMPT_SERVICE_TYPE_ID = 1 shl 10
|
||||
const val SPECIAL_USE_SERVICE_TYPE_ID = 1 shl 30
|
||||
|
||||
const val QR_CODE_NAME_PROPERTY = "# Name ="
|
||||
|
||||
const val FDROID_FLAVOR = "fdroid"
|
||||
const val GOOGLE_PLAY_FLAVOR = "google"
|
||||
const val STANDALONE_FLAVOR = "standalone"
|
||||
const val RELEASE = "release"
|
||||
|
||||
const val BASE_RELEASE_URL = "https://github.com/wgtunnel/wgtunnel/releases/tag/"
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ object DnsValidator {
|
||||
return Result.Valid
|
||||
}
|
||||
|
||||
private fun validateUdp(value: String): DnsValidator.Result {
|
||||
private fun validateUdp(value: String): Result {
|
||||
val parts = value.split(":")
|
||||
|
||||
val host = parts.getOrNull(0)?.trim()
|
||||
@@ -93,14 +93,14 @@ object DnsValidator {
|
||||
|
||||
// basic IP/hostname sanity check
|
||||
if (!isValidHostOrIp(host)) {
|
||||
return DnsValidator.Result.Invalid(DnsError.InvalidIpOrHost)
|
||||
return Result.Invalid(DnsError.InvalidIpOrHost)
|
||||
}
|
||||
|
||||
if (port !in 1..65535) {
|
||||
return DnsValidator.Result.Invalid(DnsError.InvalidPort)
|
||||
return Result.Invalid(DnsError.InvalidPort)
|
||||
}
|
||||
|
||||
return DnsValidator.Result.Valid
|
||||
return Result.Valid
|
||||
}
|
||||
|
||||
private fun isValidHostOrIp(value: String): Boolean {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,22 +1,9 @@
|
||||
package com.zaneschepke.wireguardautotunnel.util
|
||||
|
||||
import com.vdurmont.semver4j.Semver
|
||||
import java.math.BigDecimal
|
||||
import kotlin.math.pow
|
||||
import timber.log.Timber
|
||||
|
||||
object NumberUtils {
|
||||
private const val BYTES_IN_KB = 1024.0
|
||||
private val BYTES_IN_MB = BYTES_IN_KB.pow(2.0)
|
||||
private val keyValidationRegex = """^[A-Za-z0-9+/]{42}[AEIMQUYcgkosw480]=${'$'}""".toRegex()
|
||||
|
||||
fun bytesToMB(bytes: Long): BigDecimal {
|
||||
return bytes.toBigDecimal().divide(BYTES_IN_MB.toBigDecimal())
|
||||
}
|
||||
|
||||
fun isValidKey(key: String): Boolean {
|
||||
return key.matches(keyValidationRegex)
|
||||
}
|
||||
|
||||
fun generateRandomTunnelName(): String {
|
||||
return "tunnel${randomFive()}"
|
||||
|
||||
@@ -1,26 +1,18 @@
|
||||
package com.zaneschepke.wireguardautotunnel.util.extensions
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Context.POWER_SERVICE
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.PowerManager
|
||||
import android.provider.Settings
|
||||
import android.service.quicksettings.TileService
|
||||
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.service.tile.AutoTunnelControlTile
|
||||
import com.zaneschepke.wireguardautotunnel.service.tile.TunnelControlTile
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.splittunnel.state.TunnelApp
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
||||
import java.io.File
|
||||
@@ -36,11 +28,6 @@ fun Context.openWebUrl(url: String): Result<Unit> = runCatching {
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
fun Context.isBatteryOptimizationsDisabled(): Boolean {
|
||||
val pm = getSystemService(POWER_SERVICE) as PowerManager
|
||||
return pm.isIgnoringBatteryOptimizations(packageName)
|
||||
}
|
||||
|
||||
fun Context.launchNotificationSettings() {
|
||||
if (isRunningOnTv()) return launchAppSettings()
|
||||
val settingsIntent: Intent =
|
||||
@@ -87,21 +74,6 @@ fun Context.hasSAFSupport(mimeType: String): Boolean {
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.launchShareFile(file: File) {
|
||||
FileProvider.getUriForFile(this, getString(R.string.provider), file)
|
||||
val shareIntent =
|
||||
Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
type = FileUtils.ALL_FILE_TYPES
|
||||
putExtra(Intent.EXTRA_STREAM, file)
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
val chooserIntent =
|
||||
Intent.createChooser(shareIntent, "").apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }
|
||||
this.startActivity(chooserIntent)
|
||||
}
|
||||
|
||||
fun Context.launchSupportEmail(): Result<Unit> = runCatching {
|
||||
val intent =
|
||||
Intent(Intent.ACTION_SENDTO).apply {
|
||||
@@ -128,7 +100,7 @@ fun Context.isRunningOnTv(): Boolean {
|
||||
fun Context.launchVpnSettings(): Result<Unit> {
|
||||
return kotlin.runCatching {
|
||||
val intent =
|
||||
Intent(Constants.VPN_SETTINGS_PACKAGE).apply { setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }
|
||||
Intent(Constants.VPN_SETTINGS_PACKAGE).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
@@ -147,14 +119,6 @@ fun Context.launchLocationServicesSettings(): Result<Unit> {
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.launchSettings(): Result<Unit> {
|
||||
return kotlin.runCatching {
|
||||
val intent =
|
||||
Intent(Settings.ACTION_SETTINGS).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.launchAppSettings() {
|
||||
kotlin
|
||||
.runCatching {
|
||||
@@ -171,48 +135,6 @@ fun Context.launchAppSettings() {
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.requestTunnelTileServiceStateUpdate() =
|
||||
runCatching {
|
||||
TileService.requestListeningState(
|
||||
this,
|
||||
ComponentName(this, TunnelControlTile::class.java),
|
||||
)
|
||||
}
|
||||
.onFailure { Timber.w(it) }
|
||||
|
||||
fun Context.requestAutoTunnelTileServiceUpdate() =
|
||||
runCatching {
|
||||
TileService.requestListeningState(
|
||||
this,
|
||||
ComponentName(this, AutoTunnelControlTile::class.java),
|
||||
)
|
||||
}
|
||||
.onFailure { Timber.w(it) }
|
||||
|
||||
fun Context.getAllInternetCapablePackages(): List<PackageInfo> {
|
||||
val permissions = arrayOf(Manifest.permission.INTERNET)
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
packageManager.getPackagesHoldingPermissions(
|
||||
permissions,
|
||||
PackageManager.PackageInfoFlags.of(0L),
|
||||
)
|
||||
} else {
|
||||
packageManager.getPackagesHoldingPermissions(permissions, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.getSplitTunnelApps(): List<TunnelApp> {
|
||||
val packages = getAllInternetCapablePackages()
|
||||
return packages
|
||||
.filter { it.applicationInfo != null }
|
||||
.map { pkg ->
|
||||
TunnelApp(
|
||||
packageManager.getApplicationLabel(pkg.applicationInfo!!).toString(),
|
||||
pkg.packageName,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.canInstallPackages(): Boolean {
|
||||
return packageManager.canRequestPackageInstalls()
|
||||
}
|
||||
@@ -227,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,8 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.util.extensions
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
|
||||
fun <K, V> Flow<Map<K, V>>.distinctByKeys(): Flow<Map<K, V>> {
|
||||
return distinctUntilChanged { old, new -> old.keys == new.keys }
|
||||
}
|
||||
@@ -18,10 +18,6 @@ fun <T, R : Comparable<R>> List<T>.isSortedBy(selector: (T) -> R): Boolean {
|
||||
return zipWithNext().all { (a, b) -> selector(a) <= selector(b) }
|
||||
}
|
||||
|
||||
fun Int.toMillis(): Long {
|
||||
return this * 1_000L
|
||||
}
|
||||
|
||||
fun Double.round(decimals: Int): Double {
|
||||
val factor = 10.0.pow(decimals)
|
||||
return (this * factor).roundToInt() / factor
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.zaneschepke.wireguardautotunnel.util.extensions
|
||||
|
||||
import io.ktor.client.statement.HttpResponse
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
|
||||
suspend fun HttpResponse.isHtmlResponse(): Boolean {
|
||||
val contentType = headers["Content-Type"] ?: ""
|
||||
if (contentType.contains("text/html", ignoreCase = true)) return true
|
||||
|
||||
val bodyStart = bodyAsText().trimStart()
|
||||
return bodyStart.startsWith("<!DOCTYPE", ignoreCase = true) ||
|
||||
bodyStart.startsWith("<html", ignoreCase = true)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.zaneschepke.wireguardautotunnel.util.permission
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.core.content.ContextCompat
|
||||
|
||||
object LocalNetworkPermissionHelper {
|
||||
|
||||
fun shouldRequestPermission(): Boolean {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.CINNAMON_BUN
|
||||
}
|
||||
|
||||
fun isPermissionGranted(context: Context): Boolean {
|
||||
return if (shouldRequestPermission()) {
|
||||
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_LOCAL_NETWORK) ==
|
||||
PackageManager.PERMISSION_GRANTED
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,6 +145,10 @@ class AutoTunnelViewModel(
|
||||
)
|
||||
}
|
||||
|
||||
fun setDisabledOnCaptivePortal(enabled: Boolean) = intent {
|
||||
autoTunnelRepository.updateDisableOnCaptivePortal(enabled)
|
||||
}
|
||||
|
||||
fun removeTunnelNetwork(tunnel: TunnelConfig, ssid: String) = intent {
|
||||
tunnelsRepository.save(
|
||||
tunnel.copy(
|
||||
|
||||
@@ -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
|
||||
@@ -29,6 +29,7 @@ import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.QuickConfig
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelName
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asStringValue
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isHtmlResponse
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.saveTunnelsUniquely
|
||||
import com.zaneschepke.wireguardautotunnel.util.network.NetworkUtils
|
||||
import io.ktor.client.HttpClient
|
||||
@@ -60,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,
|
||||
@@ -171,7 +172,7 @@ class SharedAppViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
tunnelModeCoordinator.changeMode(mode)
|
||||
tunnelBackendCoordinator.changeMode(mode)
|
||||
}
|
||||
|
||||
fun setShouldShowDonationSnackbar(to: Boolean) = intent {
|
||||
@@ -274,17 +275,30 @@ class SharedAppViewModel(
|
||||
|
||||
fun importFromQr(conf: String) = intent { importFromClipboard(conf) }
|
||||
|
||||
fun promptWgImport(url: String) = intent { reduce { state.copy(pendingWgImportUrl = url) } }
|
||||
|
||||
fun dismissWgImport() = intent { reduce { state.copy(pendingWgImportUrl = null) } }
|
||||
|
||||
fun importFromUrl(url: String) = intent {
|
||||
reduce { state.copy(pendingWgImportUrl = null) }
|
||||
|
||||
try {
|
||||
httpClient.prepareGet(url).execute { response ->
|
||||
if (response.status.value in 200..299) {
|
||||
val body = response.bodyAsText()
|
||||
importFromClipboard(body)
|
||||
} else {
|
||||
throw IOException(
|
||||
"Failed to download file with error status: ${response.status.value}"
|
||||
)
|
||||
if (response.status.value !in 200..299) {
|
||||
throw IOException("Server returned error: ${response.status.value}")
|
||||
}
|
||||
|
||||
if (response.isHtmlResponse()) {
|
||||
postSideEffect(
|
||||
GlobalSideEffect.Snackbar(
|
||||
StringValue.StringResource(R.string.error_invalid_config_url),
|
||||
ToastType.Error,
|
||||
)
|
||||
)
|
||||
return@execute
|
||||
}
|
||||
val body = response.bodyAsText()
|
||||
importFromClipboard(body)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
|
||||
@@ -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>
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 7.8 KiB |
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:keep="@raw/aboutlibraries" />
|
||||
@@ -138,6 +138,7 @@
|
||||
<string name="config_error">Ungültige Konfiguration</string>
|
||||
<string name="join_matrix">Matrix-Community beitreten</string>
|
||||
<string name="error_download_failed">Download der Konfiguration fehlgeschlagen</string>
|
||||
<string name="wg_url_confirm_message">Möchtest du wirklich Tunnel von %1$s hinzufügen? Verbinde dich niemals mit einem nicht vertrauenswürdigen VPN!</string>
|
||||
<string name="add_from_url">Von URL hinzufügen</string>
|
||||
<string name="export_logs">Gespeicherte Logs exportieren</string>
|
||||
<string name="app_permission_title">Steuere Tunnel und Auto-Tunnel Funktionen.</string>
|
||||
|
||||
@@ -155,6 +155,7 @@
|
||||
<string name="delete">Удалить</string>
|
||||
<string name="export_failed">Экспорт не выполнен</string>
|
||||
<string name="error_download_failed">Невозможно скачать конфигурацию</string>
|
||||
<string name="wg_url_confirm_message">Добавить туннели от %1$s? Никогда не подключайтесь к неизвестному VPN!</string>
|
||||
<string name="select_all">Выбрать все</string>
|
||||
<string name="export_success">Экспорт успешно выполнен</string>
|
||||
<string name="check_for_update">Проверить обновление</string>
|
||||
|
||||
@@ -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>
|
||||
@@ -153,6 +153,7 @@
|
||||
<string name="add_from_url">Add from URL</string>
|
||||
<string name="enter_config_url">Enter config URL</string>
|
||||
<string name="error_download_failed">Failed to download config</string>
|
||||
<string name="wg_url_confirm_message">Are you sure you want to add tunnels from %1$s? Never connect to an untrusted VPN!</string>
|
||||
<string name="save">Save</string>
|
||||
<string name="search">Search</string>
|
||||
<string name="select">Select</string>
|
||||
@@ -535,4 +536,23 @@
|
||||
<string name="hide_password">Hide password</string>
|
||||
<string name="restore_failed_wrong_password">Restore failed. Wrong password</string>
|
||||
<string name="restore_failed_invalid_file">Restore failed. Select a valid backup file (.sqlite3 or .sqlite3.aes)</string>
|
||||
<string name="error_invalid_config_url">This link returned an invalid config file. Make sure you are using a direct download link</string>
|
||||
|
||||
<string name="local_network_permission_title">Local Network Access Needed</string>
|
||||
|
||||
<string name="local_network_permission_intro">WG Tunnel needs access to your local network for several features to work properly.</string>
|
||||
|
||||
<string name="local_network_permission_issues_intro">Without this permission, you may experience issues with:</string>
|
||||
|
||||
<string name="local_network_permission_feature_tunnels">- Connection issues with split tunneling, LAN bypass, or servers hosted on your local network</string>
|
||||
<string name="local_network_permission_feature_autotunnel">- Auto-tunneling and split tunneling features</string>
|
||||
<string name="local_network_permission_feature_proxy">- Local proxy and bypass functionality</string>
|
||||
|
||||
<string name="local_network_permission_recommendation">Granting this permission is strongly recommended.</string>
|
||||
<string name="local_network_permission_nearby_devices">Note: Android labels this permission as “nearby devices”.</string>
|
||||
|
||||
<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 |