mirror of
https://github.com/wgtunnel/android.git
synced 2026-07-03 14:07:49 +02:00
feat: add disable tunnel on captive portal to auto tunnel
Refactor of network monitoring and auto tunnel logic to adapt to the lest strict network monitoring of active network to support system DNS. Add disable tunnel on captive portal feature to allow auto disable of vpn while captive portal is not completed.
This commit is contained in:
@@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ import com.zaneschepke.wireguardautotunnel.data.entity.TunnelConfig
|
||||
DnsSettings::class,
|
||||
LockdownSettings::class,
|
||||
],
|
||||
version = 30,
|
||||
version = 31,
|
||||
autoMigrations =
|
||||
[
|
||||
AutoMigration(from = 1, to = 2),
|
||||
@@ -63,6 +63,7 @@ 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),
|
||||
],
|
||||
exportSchema = true,
|
||||
)
|
||||
|
||||
+3
@@ -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)
|
||||
}
|
||||
|
||||
+2
@@ -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,
|
||||
)
|
||||
|
||||
+4
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -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,
|
||||
)
|
||||
|
||||
+2
@@ -11,4 +11,6 @@ interface AutoTunnelSettingsRepository {
|
||||
suspend fun getAutoTunnelSettings(): AutoTunnelSettings
|
||||
|
||||
suspend fun updateAutoTunnelEnabled(enabled: Boolean)
|
||||
|
||||
suspend fun updateDisableOnCaptivePortal(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(),
|
||||
)
|
||||
}
|
||||
|
||||
+13
-1
@@ -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 {
|
||||
|
||||
+2
-2
@@ -192,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
|
||||
|
||||
|
||||
+16
@@ -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))
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -552,4 +552,5 @@
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
+36
-37
@@ -535,8 +535,7 @@ class AndroidNetworkMonitor(
|
||||
if (caps == null) return false
|
||||
return caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
|
||||
caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) &&
|
||||
(Build.VERSION.SDK_INT < Build.VERSION_CODES.P ||
|
||||
caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED))
|
||||
hasNotSuspended(caps)
|
||||
}
|
||||
|
||||
// default network events don't contain detailed capability information of underlying networks,
|
||||
@@ -558,23 +557,30 @@ class AndroidNetworkMonitor(
|
||||
NetworkData(defaultEvent, wifiEvent, cellularEvent, ethernetEvent)
|
||||
}
|
||||
|
||||
private fun hasNotSuspended(caps: NetworkCapabilities?): Boolean {
|
||||
if (caps == null) return false
|
||||
return Build.VERSION.SDK_INT < Build.VERSION_CODES.P ||
|
||||
caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
|
||||
}
|
||||
|
||||
// For multi-sim selection, prefers foreground, then validated internet, then not suspended
|
||||
private fun pickBestCellularNetwork(): Network? {
|
||||
private fun pickBestCellularNetworkEntry(): Map.Entry<Network, NetworkCapabilities>? {
|
||||
if (activeCellularNetworks.value.isEmpty()) return null
|
||||
|
||||
return activeCellularNetworks.value.entries
|
||||
.maxByOrNull { (_, caps) ->
|
||||
var score = 0
|
||||
if (caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOREGROUND)) score += 100
|
||||
if (hasValidatedInternet(caps)) score += 50
|
||||
if (caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED))
|
||||
score += 20
|
||||
if (caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED))
|
||||
score += 10
|
||||
if (caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) score += 5
|
||||
score
|
||||
return activeCellularNetworks.value.entries.maxByOrNull { (_, caps) ->
|
||||
var score = 0
|
||||
if (caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOREGROUND)) score += 100
|
||||
if (hasValidatedInternet(caps)) score += 50
|
||||
if (hasNotSuspended(caps)) score += 20
|
||||
if (
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P &&
|
||||
caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED)
|
||||
) {
|
||||
score += 10
|
||||
}
|
||||
?.key
|
||||
if (caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) score += 5
|
||||
score
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
@@ -610,6 +616,7 @@ class AndroidNetworkMonitor(
|
||||
if (defaultCaps == null || defaultNetwork == null) {
|
||||
return@combine ConnectivityState(
|
||||
activeNetwork = ActiveNetwork.Disconnected(),
|
||||
cellularNetworks = emptyMap(),
|
||||
locationPermissionsGranted = permissions.locationPermissionGranted,
|
||||
locationServicesEnabled = permissions.locationServicesEnabled,
|
||||
airplaneModeOn = isAirplaneOn,
|
||||
@@ -637,7 +644,10 @@ class AndroidNetworkMonitor(
|
||||
networkData.ethernetEvent.networkCapabilities?.hasTransport(
|
||||
NetworkCapabilities.TRANSPORT_ETHERNET
|
||||
) == true -> {
|
||||
ActiveNetwork.Ethernet(networkData.ethernetEvent.network)
|
||||
ActiveNetwork.Ethernet(
|
||||
networkData.ethernetEvent.network,
|
||||
networkData.ethernetEvent.networkCapabilities,
|
||||
)
|
||||
}
|
||||
|
||||
networkData.wifiNetworkEvent is TransportEvent.CapabilitiesChanged &&
|
||||
@@ -686,15 +696,19 @@ class AndroidNetworkMonitor(
|
||||
securityType,
|
||||
currentNetworkId,
|
||||
wifiEvent.network,
|
||||
wifiEvent.networkCapabilities,
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
val cellularNetwork =
|
||||
pickBestCellularNetwork()
|
||||
?: activeCellularNetworks.value.keys.firstOrNull()
|
||||
val bestCellularEntry =
|
||||
pickBestCellularNetworkEntry()
|
||||
?: activeCellularNetworks.value.entries.firstOrNull()
|
||||
|
||||
if (cellularNetwork != null) {
|
||||
ActiveNetwork.Cellular(cellularNetwork)
|
||||
if (bestCellularEntry != null) {
|
||||
ActiveNetwork.Cellular(
|
||||
bestCellularEntry.key,
|
||||
bestCellularEntry.value,
|
||||
)
|
||||
} else {
|
||||
ActiveNetwork.Disconnected()
|
||||
}
|
||||
@@ -724,23 +738,9 @@ class AndroidNetworkMonitor(
|
||||
privateDnsHostname = privateDnsSettings.hostname,
|
||||
)
|
||||
|
||||
val physicalCaps: NetworkCapabilities? =
|
||||
when (physicalNetwork) {
|
||||
is ActiveNetwork.Wifi ->
|
||||
(networkData.wifiNetworkEvent as? TransportEvent.CapabilitiesChanged)
|
||||
?.networkCapabilities
|
||||
is ActiveNetwork.Cellular ->
|
||||
activeCellularNetworks.value[physicalNetwork.network]
|
||||
is ActiveNetwork.Ethernet ->
|
||||
(networkData.ethernetEvent as? TransportEvent.CapabilitiesChanged)
|
||||
?.networkCapabilities
|
||||
else -> null
|
||||
}
|
||||
|
||||
val hasValidatedInternet = hasValidatedInternet(physicalCaps)
|
||||
|
||||
ConnectivityState(
|
||||
activeNetwork = physicalNetwork,
|
||||
cellularNetworks = activeCellularNetworks.value,
|
||||
locationPermissionsGranted = permissions.locationPermissionGranted,
|
||||
locationServicesEnabled = permissions.locationServicesEnabled,
|
||||
vpnState = vpnState,
|
||||
@@ -748,7 +748,6 @@ class AndroidNetworkMonitor(
|
||||
effectiveDnsInfo = effectiveDns,
|
||||
underlyingDnsInfo = underlyingDns,
|
||||
hasIpv6 = hasIpv6Support(underlyingNetwork, physicalNetwork),
|
||||
hasValidatedInternet = hasValidatedInternet,
|
||||
)
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package com.zaneschepke.networkmonitor
|
||||
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.os.Build
|
||||
import com.zaneschepke.networkmonitor.util.WifiSecurityType
|
||||
|
||||
data class ConnectivityState(
|
||||
val activeNetwork: ActiveNetwork,
|
||||
val cellularNetworks: Map<Network, NetworkCapabilities>,
|
||||
val locationPermissionsGranted: Boolean,
|
||||
val locationServicesEnabled: Boolean,
|
||||
val vpnState: VpnState,
|
||||
@@ -12,9 +15,56 @@ data class ConnectivityState(
|
||||
val underlyingDnsInfo: DnsInfo = DnsInfo(),
|
||||
val hasIpv6: Boolean = false,
|
||||
val airplaneModeOn: Boolean = false,
|
||||
val hasValidatedInternet: Boolean = false,
|
||||
) {
|
||||
|
||||
fun hasUsableNetwork(): Boolean {
|
||||
if (!hasActiveNetwork()) return false
|
||||
|
||||
return when (activeNetwork) {
|
||||
is ActiveNetwork.Cellular -> hasAnyUsableCellular()
|
||||
is ActiveNetwork.Wifi,
|
||||
is ActiveNetwork.Ethernet -> hasInternetCapability()
|
||||
is ActiveNetwork.Disconnected -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun requiresCaptivePortalLogin(): Boolean {
|
||||
return activeNetwork is ActiveNetwork.Wifi &&
|
||||
activeNetwork.capabilities?.hasCapability(
|
||||
NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL
|
||||
) == true
|
||||
}
|
||||
|
||||
fun hasAnyUsableCellular(): Boolean {
|
||||
if (cellularNetworks.isEmpty()) return false
|
||||
|
||||
if (cellularNetworks.values.any { hasValidatedInternet(it) }) return true
|
||||
|
||||
return cellularNetworks.values.any {
|
||||
it.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && hasNotSuspended(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasValidatedInternet(caps: NetworkCapabilities?): Boolean {
|
||||
if (caps == null) return false
|
||||
return caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
|
||||
caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) &&
|
||||
hasNotSuspended(caps)
|
||||
}
|
||||
|
||||
private fun hasInternetCapability(
|
||||
caps: NetworkCapabilities? = activeNetwork.capabilities
|
||||
): Boolean {
|
||||
if (caps == null) return false
|
||||
return caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
}
|
||||
|
||||
private fun hasNotSuspended(caps: NetworkCapabilities?): Boolean {
|
||||
if (caps == null) return false
|
||||
return Build.VERSION.SDK_INT < Build.VERSION_CODES.P ||
|
||||
caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
|
||||
}
|
||||
|
||||
fun hasActiveNetwork(): Boolean = activeNetwork !is ActiveNetwork.Disconnected
|
||||
|
||||
override fun toString(): String {
|
||||
@@ -39,6 +89,7 @@ data class Permissions(val locationServicesEnabled: Boolean, val locationPermiss
|
||||
|
||||
sealed class ActiveNetwork {
|
||||
abstract val network: Network?
|
||||
abstract val capabilities: NetworkCapabilities?
|
||||
|
||||
fun key(): String {
|
||||
return when (this) {
|
||||
@@ -49,18 +100,28 @@ sealed class ActiveNetwork {
|
||||
}
|
||||
}
|
||||
|
||||
data class Disconnected(override val network: Network? = null) : ActiveNetwork()
|
||||
data class Disconnected(
|
||||
override val network: Network? = null,
|
||||
override val capabilities: NetworkCapabilities? = null,
|
||||
) : ActiveNetwork()
|
||||
|
||||
data class Wifi(
|
||||
val ssid: String,
|
||||
val securityType: WifiSecurityType?,
|
||||
val networkId: String,
|
||||
override val network: Network?,
|
||||
override val capabilities: NetworkCapabilities? = null,
|
||||
) : ActiveNetwork()
|
||||
|
||||
data class Cellular(override val network: Network?) : ActiveNetwork()
|
||||
data class Cellular(
|
||||
override val network: Network?,
|
||||
override val capabilities: NetworkCapabilities? = null,
|
||||
) : ActiveNetwork()
|
||||
|
||||
data class Ethernet(override val network: Network?) : ActiveNetwork()
|
||||
data class Ethernet(
|
||||
override val network: Network?,
|
||||
override val capabilities: NetworkCapabilities? = null,
|
||||
) : ActiveNetwork()
|
||||
}
|
||||
|
||||
sealed interface VpnState {
|
||||
|
||||
Reference in New Issue
Block a user