mirror of
https://github.com/wgtunnel/android.git
synced 2026-07-03 14:07:49 +02:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0008573b4d | |||
| 47b2deaf3d | |||
| 99f6b438ab | |||
| f047a1e1b8 | |||
| 708b4c7646 | |||
| efba604c31 | |||
| 1441488053 | |||
| bb3b05d224 | |||
| cda747deee |
@@ -0,0 +1,267 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 13,
|
||||||
|
"identityHash": "ff209157b98a641c424f5086818ec585",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "Settings",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL, `is_tunnel_on_mobile_data_enabled` INTEGER NOT NULL, `trusted_network_ssids` TEXT NOT NULL, `is_always_on_vpn_enabled` INTEGER NOT NULL, `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL, `is_shortcuts_enabled` INTEGER NOT NULL DEFAULT false, `is_tunnel_on_wifi_enabled` INTEGER NOT NULL DEFAULT false, `is_kernel_enabled` INTEGER NOT NULL DEFAULT false, `is_restore_on_boot_enabled` INTEGER NOT NULL DEFAULT false, `is_multi_tunnel_enabled` INTEGER NOT NULL DEFAULT false, `is_ping_enabled` INTEGER NOT NULL DEFAULT false, `is_amnezia_enabled` INTEGER NOT NULL DEFAULT false, `is_wildcards_enabled` INTEGER NOT NULL DEFAULT false, `is_wifi_by_shell_enabled` INTEGER NOT NULL DEFAULT false, `is_stop_on_no_internet_enabled` INTEGER NOT NULL DEFAULT false, `is_vpn_kill_switch_enabled` INTEGER NOT NULL DEFAULT false, `is_kernel_kill_switch_enabled` INTEGER NOT NULL DEFAULT false, `is_lan_on_kill_switch_enabled` INTEGER NOT NULL DEFAULT false)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isAutoTunnelEnabled",
|
||||||
|
"columnName": "is_tunnel_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelOnMobileDataEnabled",
|
||||||
|
"columnName": "is_tunnel_on_mobile_data_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "trustedNetworkSSIDs",
|
||||||
|
"columnName": "trusted_network_ssids",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isAlwaysOnVpnEnabled",
|
||||||
|
"columnName": "is_always_on_vpn_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelOnEthernetEnabled",
|
||||||
|
"columnName": "is_tunnel_on_ethernet_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isShortcutsEnabled",
|
||||||
|
"columnName": "is_shortcuts_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isTunnelOnWifiEnabled",
|
||||||
|
"columnName": "is_tunnel_on_wifi_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isKernelEnabled",
|
||||||
|
"columnName": "is_kernel_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isRestoreOnBootEnabled",
|
||||||
|
"columnName": "is_restore_on_boot_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isMultiTunnelEnabled",
|
||||||
|
"columnName": "is_multi_tunnel_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isPingEnabled",
|
||||||
|
"columnName": "is_ping_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isAmneziaEnabled",
|
||||||
|
"columnName": "is_amnezia_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isWildcardsEnabled",
|
||||||
|
"columnName": "is_wildcards_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isWifiNameByShellEnabled",
|
||||||
|
"columnName": "is_wifi_by_shell_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isStopOnNoInternetEnabled",
|
||||||
|
"columnName": "is_stop_on_no_internet_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVpnKillSwitchEnabled",
|
||||||
|
"columnName": "is_vpn_kill_switch_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isKernelKillSwitchEnabled",
|
||||||
|
"columnName": "is_kernel_kill_switch_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isLanOnKillSwitchEnabled",
|
||||||
|
"columnName": "is_lan_on_kill_switch_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "TunnelConfig",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `wg_quick` 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, `am_quick` TEXT NOT NULL DEFAULT '', `is_Active` INTEGER NOT NULL DEFAULT false, `is_ping_enabled` INTEGER NOT NULL DEFAULT false, `ping_interval` INTEGER DEFAULT null, `ping_cooldown` INTEGER DEFAULT null, `ping_ip` TEXT DEFAULT null, `is_ethernet_tunnel` INTEGER NOT NULL DEFAULT false)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wgQuick",
|
||||||
|
"columnName": "wg_quick",
|
||||||
|
"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": "amQuick",
|
||||||
|
"columnName": "am_quick",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isActive",
|
||||||
|
"columnName": "is_Active",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isPingEnabled",
|
||||||
|
"columnName": "is_ping_enabled",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pingInterval",
|
||||||
|
"columnName": "ping_interval",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "null"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pingCooldown",
|
||||||
|
"columnName": "ping_cooldown",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "null"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pingIp",
|
||||||
|
"columnName": "ping_ip",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "null"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isEthernetTunnel",
|
||||||
|
"columnName": "is_ethernet_tunnel",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_TunnelConfig_name",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_TunnelConfig_name` ON `${TABLE_NAME}` (`name`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"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, 'ff209157b98a641c424f5086818ec585')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -204,5 +204,10 @@
|
|||||||
<action android:name="com.wireguard.android.action.REFRESH_TUNNEL_STATES" />
|
<action android:name="com.wireguard.android.action.REFRESH_TUNNEL_STATES" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
<receiver
|
||||||
|
android:name=".receiver.NotificationActionReceiver"
|
||||||
|
android:exported="false"
|
||||||
|
android:permission="${applicationId}.permission.CONTROL_TUNNELS">
|
||||||
|
</receiver>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ package com.zaneschepke.wireguardautotunnel
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.os.StrictMode
|
import android.os.StrictMode
|
||||||
import android.os.StrictMode.ThreadPolicy
|
import android.os.StrictMode.ThreadPolicy
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
|
||||||
import androidx.core.os.LocaleListCompat
|
|
||||||
import com.zaneschepke.logcatter.LogReader
|
import com.zaneschepke.logcatter.LogReader
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.BackendState
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||||
import com.zaneschepke.wireguardautotunnel.util.LocaleUtil
|
import com.zaneschepke.wireguardautotunnel.util.LocaleUtil
|
||||||
import com.zaneschepke.wireguardautotunnel.util.ReleaseTree
|
import com.zaneschepke.wireguardautotunnel.util.ReleaseTree
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||||
@@ -32,6 +33,12 @@ class WireGuardAutoTunnel : Application() {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var appStateRepository: AppStateRepository
|
lateinit var appStateRepository: AppStateRepository
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var settingsRepository: SettingsRepository
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var tunnelService: TunnelService
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@IoDispatcher
|
@IoDispatcher
|
||||||
lateinit var ioDispatcher: CoroutineDispatcher
|
lateinit var ioDispatcher: CoroutineDispatcher
|
||||||
@@ -53,10 +60,11 @@ class WireGuardAutoTunnel : Application() {
|
|||||||
Timber.plant(ReleaseTree())
|
Timber.plant(ReleaseTree())
|
||||||
}
|
}
|
||||||
applicationScope.launch {
|
applicationScope.launch {
|
||||||
|
if (!settingsRepository.getSettings().isKernelEnabled) {
|
||||||
|
tunnelService.setBackendState(BackendState.SERVICE_ACTIVE, emptyList())
|
||||||
|
}
|
||||||
appStateRepository.getLocale()?.let {
|
appStateRepository.getLocale()?.let {
|
||||||
val locale = LocaleUtil.getLocaleFromPrefCode(it)
|
LocaleUtil.changeLocale(it)
|
||||||
val appLocale: LocaleListCompat = LocaleListCompat.forLanguageTags(locale)
|
|
||||||
AppCompatDelegate.setApplicationLocales(appLocale)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isRunningOnTv()) {
|
if (!isRunningOnTv()) {
|
||||||
@@ -69,6 +77,13 @@ class WireGuardAutoTunnel : Application() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onTerminate() {
|
||||||
|
applicationScope.launch {
|
||||||
|
tunnelService.setBackendState(BackendState.INACTIVE, emptyList())
|
||||||
|
}
|
||||||
|
super.onTerminate()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
lateinit var instance: WireGuardAutoTunnel
|
lateinit var instance: WireGuardAutoTunnel
|
||||||
private set
|
private set
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
|||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
entities = [Settings::class, TunnelConfig::class],
|
entities = [Settings::class, TunnelConfig::class],
|
||||||
version = 12,
|
version = 13,
|
||||||
autoMigrations =
|
autoMigrations =
|
||||||
[
|
[
|
||||||
AutoMigration(from = 1, to = 2),
|
AutoMigration(from = 1, to = 2),
|
||||||
@@ -45,6 +45,10 @@ import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
|||||||
from = 11,
|
from = 11,
|
||||||
to = 12,
|
to = 12,
|
||||||
),
|
),
|
||||||
|
AutoMigration(
|
||||||
|
from = 12,
|
||||||
|
to = 13,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
exportSchema = true,
|
exportSchema = true,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ object Queries {
|
|||||||
VALUES
|
VALUES
|
||||||
('false',
|
('false',
|
||||||
'false',
|
'false',
|
||||||
'sampleSSID1,sampleSSID2',
|
'',
|
||||||
'false',
|
'false',
|
||||||
'false',
|
'false',
|
||||||
'false',
|
'false',
|
||||||
|
|||||||
@@ -65,4 +65,19 @@ data class Settings(
|
|||||||
defaultValue = "false",
|
defaultValue = "false",
|
||||||
)
|
)
|
||||||
val isStopOnNoInternetEnabled: Boolean = false,
|
val isStopOnNoInternetEnabled: Boolean = false,
|
||||||
|
@ColumnInfo(
|
||||||
|
name = "is_vpn_kill_switch_enabled",
|
||||||
|
defaultValue = "false",
|
||||||
|
)
|
||||||
|
val isVpnKillSwitchEnabled: Boolean = false,
|
||||||
|
@ColumnInfo(
|
||||||
|
name = "is_kernel_kill_switch_enabled",
|
||||||
|
defaultValue = "false",
|
||||||
|
)
|
||||||
|
val isKernelKillSwitchEnabled: Boolean = false,
|
||||||
|
@ColumnInfo(
|
||||||
|
name = "is_lan_on_kill_switch_enabled",
|
||||||
|
defaultValue = "false",
|
||||||
|
)
|
||||||
|
val isLanOnKillSwitchEnabled: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -92,5 +92,14 @@ data class TunnelConfig(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const val AM_QUICK_DEFAULT = ""
|
const val AM_QUICK_DEFAULT = ""
|
||||||
|
|
||||||
|
val IPV4_PUBLIC_NETWORKS = setOf(
|
||||||
|
"0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4", "32.0.0.0/3",
|
||||||
|
"64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6", "172.0.0.0/12",
|
||||||
|
"172.32.0.0/11", "172.64.0.0/10", "172.128.0.0/9", "173.0.0.0/8", "174.0.0.0/7",
|
||||||
|
"176.0.0.0/4", "192.0.0.0/9", "192.128.0.0/11", "192.160.0.0/13", "192.169.0.0/16",
|
||||||
|
"192.170.0.0/15", "192.172.0.0/14", "192.176.0.0/12", "192.192.0.0/10",
|
||||||
|
"193.0.0.0/8", "194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-4
@@ -17,10 +17,6 @@ interface AppStateRepository {
|
|||||||
|
|
||||||
suspend fun setBatteryOptimizationDisableShown(shown: Boolean)
|
suspend fun setBatteryOptimizationDisableShown(shown: Boolean)
|
||||||
|
|
||||||
suspend fun getCurrentSsid(): String?
|
|
||||||
|
|
||||||
suspend fun setCurrentSsid(ssid: String)
|
|
||||||
|
|
||||||
suspend fun isTunnelStatsExpanded(): Boolean
|
suspend fun isTunnelStatsExpanded(): Boolean
|
||||||
|
|
||||||
suspend fun setTunnelStatsExpanded(expanded: Boolean)
|
suspend fun setTunnelStatsExpanded(expanded: Boolean)
|
||||||
|
|||||||
-8
@@ -38,14 +38,6 @@ class DataStoreAppStateRepository(
|
|||||||
dataStoreManager.saveToDataStore(DataStoreManager.batteryDisableShown, shown)
|
dataStoreManager.saveToDataStore(DataStoreManager.batteryDisableShown, shown)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getCurrentSsid(): String? {
|
|
||||||
return dataStoreManager.getFromStore(DataStoreManager.currentSSID)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun setCurrentSsid(ssid: String) {
|
|
||||||
dataStoreManager.saveToDataStore(DataStoreManager.currentSSID, ssid)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun isTunnelStatsExpanded(): Boolean {
|
override suspend fun isTunnelStatsExpanded(): Boolean {
|
||||||
return dataStoreManager.getFromStore(DataStoreManager.tunnelStatsExpanded)
|
return dataStoreManager.getFromStore(DataStoreManager.tunnelStatsExpanded)
|
||||||
?: GeneralState.IS_TUNNEL_STATS_EXPANDED
|
?: GeneralState.IS_TUNNEL_STATS_EXPANDED
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package com.zaneschepke.wireguardautotunnel.module
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.zaneschepke.logcatter.LogReader
|
import com.zaneschepke.logcatter.LogReader
|
||||||
import com.zaneschepke.logcatter.LogcatCollector
|
import com.zaneschepke.logcatter.LogcatCollector
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
@@ -27,4 +29,10 @@ class AppModule {
|
|||||||
fun provideLogCollect(@ApplicationContext context: Context): LogReader {
|
fun provideLogCollect(@ApplicationContext context: Context): LogReader {
|
||||||
return LogcatCollector.init(context = context)
|
return LogcatCollector.init(context = context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideNotificationService(@ApplicationContext context: Context): NotificationService {
|
||||||
|
return WireGuardNotification(context)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import com.zaneschepke.wireguardautotunnel.service.network.EthernetService
|
|||||||
import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService
|
import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
|
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.network.WifiService
|
import com.zaneschepke.wireguardautotunnel.service.network.WifiService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
|
||||||
import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification
|
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
@@ -15,10 +13,6 @@ import dagger.hilt.android.scopes.ServiceScoped
|
|||||||
@Module
|
@Module
|
||||||
@InstallIn(ServiceComponent::class)
|
@InstallIn(ServiceComponent::class)
|
||||||
abstract class ServiceModule {
|
abstract class ServiceModule {
|
||||||
@Binds
|
|
||||||
@ServiceScoped
|
|
||||||
abstract fun provideNotificationService(wireGuardNotification: WireGuardNotification): NotificationService
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@ServiceScoped
|
@ServiceScoped
|
||||||
abstract fun provideWifiService(wifiService: WifiService): NetworkService<WifiService>
|
abstract fun provideWifiService(wifiService: WifiService): NetworkService<WifiService>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import com.wireguard.android.util.ToolsInstaller
|
|||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.WireGuardTunnel
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.WireGuardTunnel
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
@@ -76,6 +77,7 @@ class TunnelModule {
|
|||||||
@ApplicationScope applicationScope: CoroutineScope,
|
@ApplicationScope applicationScope: CoroutineScope,
|
||||||
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
||||||
serviceManager: ServiceManager,
|
serviceManager: ServiceManager,
|
||||||
|
notificationService: NotificationService,
|
||||||
): TunnelService {
|
): TunnelService {
|
||||||
return WireGuardTunnel(
|
return WireGuardTunnel(
|
||||||
amneziaBackend,
|
amneziaBackend,
|
||||||
@@ -85,12 +87,17 @@ class TunnelModule {
|
|||||||
applicationScope,
|
applicationScope,
|
||||||
ioDispatcher,
|
ioDispatcher,
|
||||||
serviceManager,
|
serviceManager,
|
||||||
|
notificationService,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideServiceManager(@ApplicationContext context: Context): ServiceManager {
|
fun provideServiceManager(
|
||||||
return ServiceManager.getInstance(context)
|
@ApplicationContext context: Context,
|
||||||
|
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
||||||
|
appDataRepository: AppDataRepository,
|
||||||
|
): ServiceManager {
|
||||||
|
return ServiceManager(context, ioDispatcher, appDataRepository)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -26,6 +26,9 @@ class KernelReceiver : BroadcastReceiver() {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var tunnelConfigRepository: TunnelConfigRepository
|
lateinit var tunnelConfigRepository: TunnelConfigRepository
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var serviceManager: ServiceManager
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
val action = intent.action ?: return
|
val action = intent.action ?: return
|
||||||
applicationScope.launch {
|
applicationScope.launch {
|
||||||
@@ -37,7 +40,7 @@ class KernelReceiver : BroadcastReceiver() {
|
|||||||
tunnelConfigRepository.save(it.copy(isActive = true))
|
tunnelConfigRepository.save(it.copy(isActive = true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
context.requestTunnelTileServiceStateUpdate()
|
serviceManager.updateTunnelTile()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.receiver
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationAction
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class NotificationActionReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var serviceManager: ServiceManager
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var tunnelService: TunnelService
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@ApplicationScope
|
||||||
|
lateinit var applicationScope: CoroutineScope
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
applicationScope.launch {
|
||||||
|
when (intent.action) {
|
||||||
|
NotificationAction.AUTO_TUNNEL_OFF.name -> serviceManager.stopAutoTunnel()
|
||||||
|
NotificationAction.TUNNEL_OFF.name -> tunnelService.stopTunnel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+41
-9
@@ -3,20 +3,25 @@ package com.zaneschepke.wireguardautotunnel.service.foreground
|
|||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.AutoTunnelService
|
import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.AutoTunnelService
|
||||||
import com.zaneschepke.wireguardautotunnel.util.SingletonHolder
|
import com.zaneschepke.wireguardautotunnel.service.tile.AutoTunnelControlTile
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.tile.TunnelControlTile
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.requestAutoTunnelTileServiceUpdate
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate
|
import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate
|
||||||
import jakarta.inject.Inject
|
import jakarta.inject.Inject
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class ServiceManager
|
class ServiceManager
|
||||||
@Inject constructor(private val context: Context) {
|
@Inject constructor(private val context: Context, private val ioDispatcher: CoroutineDispatcher, private val appDataRepository: AppDataRepository) {
|
||||||
|
|
||||||
private val _autoTunnelActive = MutableStateFlow(false)
|
private val _autoTunnelActive = MutableStateFlow(false)
|
||||||
|
|
||||||
@@ -24,8 +29,8 @@ class ServiceManager
|
|||||||
|
|
||||||
var autoTunnelService = CompletableDeferred<AutoTunnelService>()
|
var autoTunnelService = CompletableDeferred<AutoTunnelService>()
|
||||||
var backgroundService = CompletableDeferred<TunnelBackgroundService>()
|
var backgroundService = CompletableDeferred<TunnelBackgroundService>()
|
||||||
|
var autoTunnelTile = CompletableDeferred<AutoTunnelControlTile>()
|
||||||
companion object : SingletonHolder<ServiceManager, Context>(::ServiceManager)
|
var tunnelControlTile = CompletableDeferred<TunnelControlTile>()
|
||||||
|
|
||||||
private fun <T : Service> startService(cls: Class<T>, background: Boolean) {
|
private fun <T : Service> startService(cls: Class<T>, background: Boolean) {
|
||||||
runCatching {
|
runCatching {
|
||||||
@@ -39,12 +44,15 @@ class ServiceManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun startAutoTunnel(background: Boolean) {
|
suspend fun startAutoTunnel(background: Boolean) {
|
||||||
|
val settings = appDataRepository.settings.getSettings()
|
||||||
|
appDataRepository.settings.save(settings.copy(isAutoTunnelEnabled = true))
|
||||||
if (autoTunnelService.isCompleted) return _autoTunnelActive.update { true }
|
if (autoTunnelService.isCompleted) return _autoTunnelActive.update { true }
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
startService(AutoTunnelService::class.java, background)
|
startService(AutoTunnelService::class.java, background)
|
||||||
autoTunnelService.await()
|
autoTunnelService.await()
|
||||||
autoTunnelService.getCompleted().start()
|
autoTunnelService.getCompleted().start()
|
||||||
_autoTunnelActive.update { true }
|
_autoTunnelActive.update { true }
|
||||||
|
updateAutoTunnelTile()
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Timber.e(it)
|
Timber.e(it)
|
||||||
}
|
}
|
||||||
@@ -70,17 +78,41 @@ class ServiceManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopAutoTunnel() {
|
suspend fun toggleAutoTunnel(background: Boolean) {
|
||||||
if (!autoTunnelService.isCompleted) return
|
withContext(ioDispatcher) {
|
||||||
|
if (_autoTunnelActive.value) return@withContext stopAutoTunnel()
|
||||||
|
startAutoTunnel(background)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateAutoTunnelTile() {
|
||||||
|
if (autoTunnelTile.isCompleted) {
|
||||||
|
autoTunnelTile.getCompleted().updateTileState()
|
||||||
|
} else {
|
||||||
|
context.requestAutoTunnelTileServiceUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateTunnelTile() {
|
||||||
|
if (tunnelControlTile.isCompleted) {
|
||||||
|
tunnelControlTile.getCompleted().updateTileState()
|
||||||
|
} else {
|
||||||
|
context.requestTunnelTileServiceStateUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun stopAutoTunnel() {
|
||||||
|
withContext(ioDispatcher) {
|
||||||
|
val settings = appDataRepository.settings.getSettings()
|
||||||
|
appDataRepository.settings.save(settings.copy(isAutoTunnelEnabled = false))
|
||||||
|
if (!autoTunnelService.isCompleted) return@withContext
|
||||||
runCatching {
|
runCatching {
|
||||||
autoTunnelService.getCompleted().stop()
|
autoTunnelService.getCompleted().stop()
|
||||||
_autoTunnelActive.update { false }
|
_autoTunnelActive.update { false }
|
||||||
|
updateAutoTunnelTile()
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Timber.e(it)
|
Timber.e(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestTunnelTileUpdate() {
|
|
||||||
context.requestTunnelTileServiceStateUpdate()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-5
@@ -7,6 +7,7 @@ import androidx.core.app.ServiceCompat
|
|||||||
import androidx.lifecycle.LifecycleService
|
import androidx.lifecycle.LifecycleService
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
@@ -21,8 +22,6 @@ class TunnelBackgroundService : LifecycleService() {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var serviceManager: ServiceManager
|
lateinit var serviceManager: ServiceManager
|
||||||
|
|
||||||
private val foregroundId = 123
|
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
start()
|
start()
|
||||||
@@ -42,7 +41,7 @@ class TunnelBackgroundService : LifecycleService() {
|
|||||||
fun start() {
|
fun start() {
|
||||||
ServiceCompat.startForeground(
|
ServiceCompat.startForeground(
|
||||||
this,
|
this,
|
||||||
foregroundId,
|
NotificationService.KERNEL_SERVICE_NOTIFICATION_ID,
|
||||||
createNotification(),
|
createNotification(),
|
||||||
Constants.SYSTEM_EXEMPT_SERVICE_TYPE_ID,
|
Constants.SYSTEM_EXEMPT_SERVICE_TYPE_ID,
|
||||||
)
|
)
|
||||||
@@ -60,8 +59,7 @@ class TunnelBackgroundService : LifecycleService() {
|
|||||||
|
|
||||||
private fun createNotification(): Notification {
|
private fun createNotification(): Notification {
|
||||||
return notificationService.createNotification(
|
return notificationService.createNotification(
|
||||||
getString(R.string.vpn_channel_id),
|
WireGuardNotification.NotificationChannels.VPN,
|
||||||
getString(R.string.vpn_channel_name),
|
|
||||||
getString(R.string.tunnel_running),
|
getString(R.string.tunnel_running),
|
||||||
description = "",
|
description = "",
|
||||||
)
|
)
|
||||||
|
|||||||
+63
-190
@@ -9,20 +9,27 @@ import androidx.lifecycle.LifecycleService
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.wireguard.android.util.RootShell
|
import com.wireguard.android.util.RootShell
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.module.AppShell
|
import com.zaneschepke.wireguardautotunnel.module.AppShell
|
||||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.module.MainImmediateDispatcher
|
import com.zaneschepke.wireguardautotunnel.module.MainImmediateDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.AutoTunnelEvent
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.AutoTunnelState
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.NetworkState
|
||||||
import com.zaneschepke.wireguardautotunnel.service.network.EthernetService
|
import com.zaneschepke.wireguardautotunnel.service.network.EthernetService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService
|
import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
|
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.network.NetworkStatus
|
import com.zaneschepke.wireguardautotunnel.service.network.NetworkStatus
|
||||||
import com.zaneschepke.wireguardautotunnel.service.network.WifiService
|
import com.zaneschepke.wireguardautotunnel.service.network.WifiService
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationAction
|
||||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage
|
import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.getCurrentWifiName
|
import com.zaneschepke.wireguardautotunnel.util.extensions.getCurrentWifiName
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable
|
||||||
@@ -32,10 +39,11 @@ import kotlinx.coroutines.CompletableDeferred
|
|||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.collect
|
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.filterNot
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -47,7 +55,6 @@ import javax.inject.Provider
|
|||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class AutoTunnelService : LifecycleService() {
|
class AutoTunnelService : LifecycleService() {
|
||||||
private val foregroundId = 122
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@AppShell
|
@AppShell
|
||||||
@@ -63,7 +70,7 @@ class AutoTunnelService : LifecycleService() {
|
|||||||
lateinit var ethernetService: NetworkService<EthernetService>
|
lateinit var ethernetService: NetworkService<EthernetService>
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var appDataRepository: AppDataRepository
|
lateinit var appDataRepository: Provider<AppDataRepository>
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var notificationService: NotificationService
|
lateinit var notificationService: NotificationService
|
||||||
@@ -92,6 +99,7 @@ class AutoTunnelService : LifecycleService() {
|
|||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
serviceManager.autoTunnelService.complete(this)
|
||||||
lifecycleScope.launch(mainImmediateDispatcher) {
|
lifecycleScope.launch(mainImmediateDispatcher) {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
launchWatcherNotification()
|
launchWatcherNotification()
|
||||||
@@ -103,7 +111,6 @@ class AutoTunnelService : LifecycleService() {
|
|||||||
|
|
||||||
override fun onBind(intent: Intent): IBinder? {
|
override fun onBind(intent: Intent): IBinder? {
|
||||||
super.onBind(intent)
|
super.onBind(intent)
|
||||||
// We don't provide binding, so return null
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,9 +126,8 @@ class AutoTunnelService : LifecycleService() {
|
|||||||
launchWatcherNotification()
|
launchWatcherNotification()
|
||||||
initWakeLock()
|
initWakeLock()
|
||||||
}
|
}
|
||||||
startSettingsJob()
|
startAutoTunnelJob()
|
||||||
startVpnStateJob()
|
startAutoTunnelStateJob()
|
||||||
startNetworkJobs()
|
|
||||||
startPingStateJob()
|
startPingStateJob()
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Timber.e(it)
|
Timber.e(it)
|
||||||
@@ -129,11 +135,7 @@ class AutoTunnelService : LifecycleService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
wakeLock?.let {
|
wakeLock?.let { if (it.isHeld) it.release() }
|
||||||
if (it.isHeld) {
|
|
||||||
it.release()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stopSelf()
|
stopSelf()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,22 +148,23 @@ class AutoTunnelService : LifecycleService() {
|
|||||||
private fun launchWatcherNotification(description: String = getString(R.string.monitoring_state_changes)) {
|
private fun launchWatcherNotification(description: String = getString(R.string.monitoring_state_changes)) {
|
||||||
val notification =
|
val notification =
|
||||||
notificationService.createNotification(
|
notificationService.createNotification(
|
||||||
channelId = getString(R.string.watcher_channel_id),
|
WireGuardNotification.NotificationChannels.AUTO_TUNNEL,
|
||||||
channelName = getString(R.string.watcher_channel_name),
|
|
||||||
title = getString(R.string.auto_tunnel_title),
|
title = getString(R.string.auto_tunnel_title),
|
||||||
description = description,
|
description = description,
|
||||||
|
actions = listOf(
|
||||||
|
notificationService.createNotificationAction(NotificationAction.AUTO_TUNNEL_OFF),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
ServiceCompat.startForeground(
|
ServiceCompat.startForeground(
|
||||||
this,
|
this,
|
||||||
foregroundId,
|
NotificationService.AUTO_TUNNEL_NOTIFICATION_ID,
|
||||||
notification,
|
notification,
|
||||||
Constants.SYSTEM_EXEMPT_SERVICE_TYPE_ID,
|
Constants.SYSTEM_EXEMPT_SERVICE_TYPE_ID,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initWakeLock() {
|
private fun initWakeLock() {
|
||||||
wakeLock =
|
wakeLock = (getSystemService(POWER_SERVICE) as PowerManager).run {
|
||||||
(getSystemService(POWER_SERVICE) as PowerManager).run {
|
|
||||||
val tag = this.javaClass.name
|
val tag = this.javaClass.name
|
||||||
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "$tag::lock").apply {
|
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "$tag::lock").apply {
|
||||||
try {
|
try {
|
||||||
@@ -174,34 +177,10 @@ class AutoTunnelService : LifecycleService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startSettingsJob() = lifecycleScope.launch {
|
|
||||||
watchForSettingsChanges()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startVpnStateJob() = lifecycleScope.launch {
|
|
||||||
watchForVpnStateChanges()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startWifiJob() = lifecycleScope.launch {
|
|
||||||
watchForWifiConnectivityChanges()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startMobileDataJob() = lifecycleScope.launch {
|
|
||||||
watchForMobileDataConnectivityChanges()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startEthernetJob() = lifecycleScope.launch {
|
|
||||||
watchForEthernetConnectivityChanges()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startPingJob() = lifecycleScope.launch {
|
private fun startPingJob() = lifecycleScope.launch {
|
||||||
watchForPingFailure()
|
watchForPingFailure()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startNetworkEventJob() = lifecycleScope.launch {
|
|
||||||
handleNetworkEventChanges()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startPingStateJob() = lifecycleScope.launch {
|
private fun startPingStateJob() = lifecycleScope.launch {
|
||||||
autoTunnelStateFlow.collect {
|
autoTunnelStateFlow.collect {
|
||||||
if (it.isPingEnabled()) {
|
if (it.isPingEnabled()) {
|
||||||
@@ -212,30 +191,6 @@ class AutoTunnelService : LifecycleService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun watchForMobileDataConnectivityChanges() {
|
|
||||||
withContext(ioDispatcher) {
|
|
||||||
Timber.i("Starting mobile data watcher")
|
|
||||||
mobileDataService.networkStatus.collect { status ->
|
|
||||||
when (status) {
|
|
||||||
is NetworkStatus.Available -> {
|
|
||||||
Timber.i("Gained Mobile data connection")
|
|
||||||
emitMobileDataConnected(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
is NetworkStatus.CapabilitiesChanged -> {
|
|
||||||
emitMobileDataConnected(true)
|
|
||||||
Timber.i("Mobile data capabilities changed")
|
|
||||||
}
|
|
||||||
|
|
||||||
is NetworkStatus.Unavailable -> {
|
|
||||||
emitMobileDataConnected(false)
|
|
||||||
Timber.i("Lost mobile data connection")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun watchForPingFailure() {
|
private suspend fun watchForPingFailure() {
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
Timber.i("Starting ping watcher")
|
Timber.i("Starting ping watcher")
|
||||||
@@ -274,136 +229,52 @@ class AutoTunnelService : LifecycleService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun watchForSettingsChanges() {
|
private fun startAutoTunnelStateJob() = lifecycleScope.launch(ioDispatcher) {
|
||||||
Timber.i("Starting settings watcher")
|
combine(
|
||||||
withContext(ioDispatcher) {
|
combineSettings(),
|
||||||
appDataRepository.settings.getSettingsFlow().combine(
|
combineNetworkEventsJob(),
|
||||||
appDataRepository.tunnels.getTunnelConfigsFlow(),
|
) { double, networkState ->
|
||||||
) { settings, tunnels ->
|
AutoTunnelState(tunnelService.get().vpnState.value, networkState, double.first, double.second)
|
||||||
Pair(settings, tunnels)
|
}.collect { state ->
|
||||||
}.collect { pair ->
|
|
||||||
autoTunnelStateFlow.update {
|
autoTunnelStateFlow.update {
|
||||||
it.copy(
|
it.copy(state.vpnState, state.networkState, state.settings, state.tunnels)
|
||||||
settings = pair.first,
|
|
||||||
tunnels = pair.second,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun watchForVpnStateChanges() {
|
|
||||||
Timber.i("Starting vpn state watcher")
|
|
||||||
withContext(ioDispatcher) {
|
|
||||||
tunnelService.get().vpnState.collect { state ->
|
|
||||||
autoTunnelStateFlow.update {
|
|
||||||
it.copy(vpnState = state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startNetworkJobs() {
|
|
||||||
Timber.i("Starting all network state jobs..")
|
|
||||||
startWifiJob()
|
|
||||||
startEthernetJob()
|
|
||||||
startMobileDataJob()
|
|
||||||
startNetworkEventJob()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun cancelAndResetPingJob() {
|
private fun cancelAndResetPingJob() {
|
||||||
pingJob?.cancelWithMessage("Ping job canceled")
|
pingJob?.cancelWithMessage("Ping job canceled")
|
||||||
pingJob = null
|
pingJob = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun emitEthernetConnected(connected: Boolean) {
|
private fun combineNetworkEventsJob(): Flow<NetworkState> {
|
||||||
autoTunnelStateFlow.update {
|
return combine(
|
||||||
it.copy(
|
wifiService.networkStatus,
|
||||||
isEthernetConnected = connected,
|
mobileDataService.networkStatus,
|
||||||
|
ethernetService.networkStatus,
|
||||||
|
) { wifi, mobileData, ethernet ->
|
||||||
|
NetworkState(
|
||||||
|
wifi.isConnected,
|
||||||
|
mobileData.isConnected,
|
||||||
|
ethernet.isConnected,
|
||||||
|
when (wifi) {
|
||||||
|
is NetworkStatus.CapabilitiesChanged -> getWifiSSID(wifi.networkCapabilities)
|
||||||
|
is NetworkStatus.Available -> autoTunnelStateFlow.value.networkState.wifiName
|
||||||
|
is NetworkStatus.Unavailable -> null
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}.distinctUntilChanged().filterNot { it.isWifiConnected && it.wifiName == null }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun emitWifiConnected(connected: Boolean) {
|
private fun combineSettings(): Flow<Pair<Settings, TunnelConfigs>> {
|
||||||
autoTunnelStateFlow.update {
|
return combine(
|
||||||
it.copy(
|
appDataRepository.get().settings.getSettingsFlow(),
|
||||||
isWifiConnected = connected,
|
appDataRepository.get().tunnels.getTunnelConfigsFlow().distinctUntilChanged { old, new ->
|
||||||
)
|
old.map { it.isActive } != new.map { it.isActive }
|
||||||
}
|
},
|
||||||
}
|
) { settings, tunnels ->
|
||||||
|
Pair(settings, tunnels)
|
||||||
private fun emitWifiSSID(ssid: String) {
|
}.distinctUntilChanged()
|
||||||
autoTunnelStateFlow.update {
|
|
||||||
it.copy(
|
|
||||||
currentNetworkSSID = ssid,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun emitMobileDataConnected(connected: Boolean) {
|
|
||||||
autoTunnelStateFlow.update {
|
|
||||||
it.copy(
|
|
||||||
isMobileDataConnected = connected,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun watchForEthernetConnectivityChanges() {
|
|
||||||
withContext(ioDispatcher) {
|
|
||||||
Timber.i("Starting ethernet data watcher")
|
|
||||||
ethernetService.networkStatus.collect { status ->
|
|
||||||
when (status) {
|
|
||||||
is NetworkStatus.Available -> {
|
|
||||||
Timber.i("Gained Ethernet connection")
|
|
||||||
emitEthernetConnected(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
is NetworkStatus.CapabilitiesChanged -> {
|
|
||||||
Timber.i("Ethernet capabilities changed")
|
|
||||||
emitEthernetConnected(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
is NetworkStatus.Unavailable -> {
|
|
||||||
emitEthernetConnected(false)
|
|
||||||
Timber.i("Lost Ethernet connection")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun watchForWifiConnectivityChanges() {
|
|
||||||
withContext(ioDispatcher) {
|
|
||||||
Timber.i("Starting wifi watcher")
|
|
||||||
wifiService.networkStatus.collect { status ->
|
|
||||||
when (status) {
|
|
||||||
is NetworkStatus.Available -> {
|
|
||||||
Timber.i("Gained Wi-Fi connection")
|
|
||||||
emitWifiConnected(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
is NetworkStatus.CapabilitiesChanged -> {
|
|
||||||
Timber.i("Wifi capabilities changed")
|
|
||||||
emitWifiConnected(true)
|
|
||||||
val ssid = getWifiSSID(status.networkCapabilities)
|
|
||||||
ssid?.let { name ->
|
|
||||||
if (name.contains(Constants.UNREADABLE_SSID)) {
|
|
||||||
Timber.w("SSID unreadable: missing permissions")
|
|
||||||
} else {
|
|
||||||
Timber.i("Detected valid SSID")
|
|
||||||
}
|
|
||||||
appDataRepository.appState.setCurrentSsid(name)
|
|
||||||
emitWifiSSID(name)
|
|
||||||
} ?: Timber.w("Failed to read ssid")
|
|
||||||
}
|
|
||||||
|
|
||||||
is NetworkStatus.Unavailable -> {
|
|
||||||
emitWifiConnected(false)
|
|
||||||
Timber.i("Lost Wi-Fi connection")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getWifiSSID(networkCapabilities: NetworkCapabilities): String? {
|
private suspend fun getWifiSSID(networkCapabilities: NetworkCapabilities): String? {
|
||||||
@@ -411,21 +282,24 @@ class AutoTunnelService : LifecycleService() {
|
|||||||
with(autoTunnelStateFlow.value.settings) {
|
with(autoTunnelStateFlow.value.settings) {
|
||||||
if (isWifiNameByShellEnabled) return@withContext rootShell.get().getCurrentWifiName()
|
if (isWifiNameByShellEnabled) return@withContext rootShell.get().getCurrentWifiName()
|
||||||
wifiService.getNetworkName(networkCapabilities)
|
wifiService.getNetworkName(networkCapabilities)
|
||||||
|
}.also {
|
||||||
|
if (it?.contains(Constants.UNREADABLE_SSID) == true) {
|
||||||
|
Timber.w("SSID unreadable: missing permissions")
|
||||||
|
} else {
|
||||||
|
Timber.i("Detected valid SSID")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleNetworkEventChanges() {
|
private fun startAutoTunnelJob() = lifecycleScope.launch(ioDispatcher) {
|
||||||
withContext(ioDispatcher) {
|
|
||||||
Timber.i("Starting auto-tunnel network event watcher")
|
Timber.i("Starting auto-tunnel network event watcher")
|
||||||
// ignore vpnState emits to allow manual overrides
|
autoTunnelStateFlow.collect { watcherState ->
|
||||||
autoTunnelStateFlow.distinctUntilChanged { old, new ->
|
Timber.d("New auto tunnel state emitted")
|
||||||
old.copy(vpnState = new.vpnState) == new || old.tunnels.map { it.isActive } != new.tunnels.map { it.isActive }
|
|
||||||
}.collect { watcherState ->
|
|
||||||
when (val event = watcherState.asAutoTunnelEvent()) {
|
when (val event = watcherState.asAutoTunnelEvent()) {
|
||||||
is AutoTunnelEvent.Start -> tunnelService.get().startTunnel(
|
is AutoTunnelEvent.Start -> tunnelService.get().startTunnel(
|
||||||
event.tunnelConfig
|
event.tunnelConfig
|
||||||
?: appDataRepository.getPrimaryOrFirstTunnel(),
|
?: appDataRepository.get().getPrimaryOrFirstTunnel(),
|
||||||
)
|
)
|
||||||
is AutoTunnelEvent.Stop -> tunnelService.get().stopTunnel()
|
is AutoTunnelEvent.Stop -> tunnelService.get().stopTunnel()
|
||||||
AutoTunnelEvent.DoNothing -> Timber.i("Auto-tunneling: no condition met")
|
AutoTunnelEvent.DoNothing -> Timber.i("Auto-tunneling: no condition met")
|
||||||
@@ -433,4 +307,3 @@ class AutoTunnelService : LifecycleService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel
|
package com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
|
|
||||||
+21
-18
@@ -1,23 +1,21 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel
|
package com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnState
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnState
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
|
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
data class AutoTunnelState(
|
data class AutoTunnelState(
|
||||||
val vpnState: VpnState = VpnState(),
|
val vpnState: VpnState = VpnState(),
|
||||||
val isWifiConnected: Boolean = false,
|
val networkState: NetworkState = NetworkState(),
|
||||||
val isEthernetConnected: Boolean = false,
|
|
||||||
val isMobileDataConnected: Boolean = false,
|
|
||||||
val currentNetworkSSID: String = "",
|
|
||||||
val settings: Settings = Settings(),
|
val settings: Settings = Settings(),
|
||||||
val tunnels: TunnelConfigs = emptyList(),
|
val tunnels: TunnelConfigs = emptyList(),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private fun isMobileDataActive(): Boolean {
|
private fun isMobileDataActive(): Boolean {
|
||||||
return !isEthernetConnected && !isWifiConnected && isMobileDataConnected
|
return !networkState.isEthernetConnected && !networkState.isWifiConnected && networkState.isMobileDataConnected
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isMobileTunnelDataChangeNeeded(): Boolean {
|
private fun isMobileTunnelDataChangeNeeded(): Boolean {
|
||||||
@@ -44,19 +42,19 @@ data class AutoTunnelState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun isWifiActive(): Boolean {
|
private fun isWifiActive(): Boolean {
|
||||||
return !isEthernetConnected && isWifiConnected
|
return !networkState.isEthernetConnected && networkState.isWifiConnected
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startOnEthernet(): Boolean {
|
private fun startOnEthernet(): Boolean {
|
||||||
return isEthernetConnected && settings.isTunnelOnEthernetEnabled && vpnState.status.isDown()
|
return networkState.isEthernetConnected && settings.isTunnelOnEthernetEnabled && vpnState.status.isDown()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stopOnEthernet(): Boolean {
|
private fun stopOnEthernet(): Boolean {
|
||||||
return isEthernetConnected && !settings.isTunnelOnEthernetEnabled && vpnState.status.isUp()
|
return networkState.isEthernetConnected && !settings.isTunnelOnEthernetEnabled && vpnState.status.isUp()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isNoConnectivity(): Boolean {
|
fun isNoConnectivity(): Boolean {
|
||||||
return !isEthernetConnected && !isWifiConnected && !isMobileDataConnected
|
return !networkState.isEthernetConnected && !networkState.isWifiConnected && !networkState.isMobileDataConnected
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stopOnMobileData(): Boolean {
|
private fun stopOnMobileData(): Boolean {
|
||||||
@@ -72,7 +70,7 @@ data class AutoTunnelState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun changeOnEthernet(): Boolean {
|
private fun changeOnEthernet(): Boolean {
|
||||||
return isEthernetConnected && settings.isTunnelOnEthernetEnabled && isEthernetTunnelChangeNeeded()
|
return networkState.isEthernetConnected && settings.isTunnelOnEthernetEnabled && isEthernetTunnelChangeNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stopOnWifi(): Boolean {
|
private fun stopOnWifi(): Boolean {
|
||||||
@@ -84,6 +82,7 @@ data class AutoTunnelState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun startOnUntrustedWifi(): Boolean {
|
private fun startOnUntrustedWifi(): Boolean {
|
||||||
|
Timber.d("Is tunnel on wifi enabled ${settings.isTunnelOnWifiEnabled}")
|
||||||
return isWifiActive() && settings.isTunnelOnWifiEnabled && vpnState.status.isDown() && !isCurrentSSIDTrusted()
|
return isWifiActive() && settings.isTunnelOnWifiEnabled && vpnState.status.isDown() && !isCurrentSSIDTrusted()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,19 +119,23 @@ data class AutoTunnelState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun isCurrentSSIDTrusted(): Boolean {
|
private fun isCurrentSSIDTrusted(): Boolean {
|
||||||
|
return networkState.wifiName?.let {
|
||||||
|
hasTrustedWifiName(it)
|
||||||
|
} == true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hasTrustedWifiName(wifiName: String, wifiNames: List<String> = settings.trustedNetworkSSIDs): Boolean {
|
||||||
return if (settings.isWildcardsEnabled) {
|
return if (settings.isWildcardsEnabled) {
|
||||||
settings.trustedNetworkSSIDs.isMatchingToWildcardList(currentNetworkSSID)
|
wifiNames.isMatchingToWildcardList(wifiName)
|
||||||
} else {
|
} else {
|
||||||
settings.trustedNetworkSSIDs.contains(currentNetworkSSID)
|
wifiNames.contains(wifiName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTunnelWithMatchingTunnelNetwork(): TunnelConfig? {
|
private fun getTunnelWithMatchingTunnelNetwork(): TunnelConfig? {
|
||||||
return tunnels.firstOrNull {
|
return networkState.wifiName?.let { wifiName ->
|
||||||
if (settings.isWildcardsEnabled) {
|
tunnels.firstOrNull {
|
||||||
it.tunnelNetworks.isMatchingToWildcardList(currentNetworkSSID)
|
hasTrustedWifiName(wifiName, it.tunnelNetworks)
|
||||||
} else {
|
|
||||||
it.tunnelNetworks.contains(currentNetworkSSID)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model
|
||||||
|
|
||||||
|
data class NetworkState(
|
||||||
|
val isWifiConnected: Boolean = false,
|
||||||
|
val isMobileDataConnected: Boolean = false,
|
||||||
|
val isEthernetConnected: Boolean = false,
|
||||||
|
val wifiName: String? = null,
|
||||||
|
)
|
||||||
+20
-5
@@ -10,7 +10,10 @@ import android.os.Build
|
|||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.conflate
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
abstract class BaseNetworkService<T : BaseNetworkService<T>>(
|
abstract class BaseNetworkService<T : BaseNetworkService<T>>(
|
||||||
val context: Context,
|
val context: Context,
|
||||||
@@ -22,8 +25,17 @@ abstract class BaseNetworkService<T : BaseNetworkService<T>>(
|
|||||||
val wifiManager =
|
val wifiManager =
|
||||||
context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
|
context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
|
||||||
|
|
||||||
|
fun checkHasCapability(networkCapability: Int): Boolean {
|
||||||
|
val network = connectivityManager.activeNetwork
|
||||||
|
val networkCapabilities = connectivityManager.getNetworkCapabilities(network)
|
||||||
|
return networkCapabilities?.hasTransport(networkCapability) == true
|
||||||
|
}
|
||||||
|
|
||||||
override val networkStatus =
|
override val networkStatus =
|
||||||
callbackFlow {
|
callbackFlow {
|
||||||
|
if (!checkHasCapability(networkCapability)) {
|
||||||
|
trySend(NetworkStatus.Unavailable())
|
||||||
|
}
|
||||||
val networkStatusCallback =
|
val networkStatusCallback =
|
||||||
when (Build.VERSION.SDK_INT) {
|
when (Build.VERSION.SDK_INT) {
|
||||||
in Build.VERSION_CODES.S..Int.MAX_VALUE -> {
|
in Build.VERSION_CODES.S..Int.MAX_VALUE -> {
|
||||||
@@ -36,7 +48,7 @@ abstract class BaseNetworkService<T : BaseNetworkService<T>>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onLost(network: Network) {
|
override fun onLost(network: Network) {
|
||||||
trySend(NetworkStatus.Unavailable(network))
|
trySend(NetworkStatus.Unavailable())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||||
@@ -57,7 +69,7 @@ abstract class BaseNetworkService<T : BaseNetworkService<T>>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onLost(network: Network) {
|
override fun onLost(network: Network) {
|
||||||
trySend(NetworkStatus.Unavailable(network))
|
trySend(NetworkStatus.Unavailable())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||||
@@ -80,17 +92,20 @@ abstract class BaseNetworkService<T : BaseNetworkService<T>>(
|
|||||||
connectivityManager.registerNetworkCallback(request, networkStatusCallback)
|
connectivityManager.registerNetworkCallback(request, networkStatusCallback)
|
||||||
|
|
||||||
awaitClose { connectivityManager.unregisterNetworkCallback(networkStatusCallback) }
|
awaitClose { connectivityManager.unregisterNetworkCallback(networkStatusCallback) }
|
||||||
}
|
}.catch {
|
||||||
|
Timber.e(it)
|
||||||
|
// conflate for backpressure
|
||||||
|
}.conflate()
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <Result> Flow<NetworkStatus>.map(
|
inline fun <Result> Flow<NetworkStatus>.map(
|
||||||
crossinline onUnavailable: suspend (network: Network) -> Result,
|
crossinline onUnavailable: suspend () -> Result,
|
||||||
crossinline onAvailable: suspend (network: Network) -> Result,
|
crossinline onAvailable: suspend (network: Network) -> Result,
|
||||||
crossinline onCapabilitiesChanged:
|
crossinline onCapabilitiesChanged:
|
||||||
suspend (network: Network, networkCapabilities: NetworkCapabilities) -> Result,
|
suspend (network: Network, networkCapabilities: NetworkCapabilities) -> Result,
|
||||||
): Flow<Result> = map { status ->
|
): Flow<Result> = map { status ->
|
||||||
when (status) {
|
when (status) {
|
||||||
is NetworkStatus.Unavailable -> onUnavailable(status.network)
|
is NetworkStatus.Unavailable -> onUnavailable()
|
||||||
is NetworkStatus.Available -> onAvailable(status.network)
|
is NetworkStatus.Available -> onAvailable(status.network)
|
||||||
is NetworkStatus.CapabilitiesChanged ->
|
is NetworkStatus.CapabilitiesChanged ->
|
||||||
onCapabilitiesChanged(
|
onCapabilitiesChanged(
|
||||||
|
|||||||
+4
-3
@@ -4,10 +4,11 @@ import android.net.Network
|
|||||||
import android.net.NetworkCapabilities
|
import android.net.NetworkCapabilities
|
||||||
|
|
||||||
sealed class NetworkStatus {
|
sealed class NetworkStatus {
|
||||||
class Available(val network: Network) : NetworkStatus()
|
abstract val isConnected: Boolean
|
||||||
|
class Available(val network: Network, override val isConnected: Boolean = true) : NetworkStatus()
|
||||||
|
|
||||||
class Unavailable(val network: Network) : NetworkStatus()
|
class Unavailable(override val isConnected: Boolean = false) : NetworkStatus()
|
||||||
|
|
||||||
class CapabilitiesChanged(val network: Network, val networkCapabilities: NetworkCapabilities) :
|
class CapabilitiesChanged(val network: Network, val networkCapabilities: NetworkCapabilities, override val isConnected: Boolean = true) :
|
||||||
NetworkStatus()
|
NetworkStatus()
|
||||||
}
|
}
|
||||||
|
|||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.service.notification
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
|
|
||||||
|
enum class NotificationAction {
|
||||||
|
TUNNEL_OFF,
|
||||||
|
AUTO_TUNNEL_OFF,
|
||||||
|
;
|
||||||
|
|
||||||
|
fun title(context: Context): String {
|
||||||
|
return when (this) {
|
||||||
|
TUNNEL_OFF -> context.getString(R.string.stop)
|
||||||
|
AUTO_TUNNEL_OFF -> context.getString(R.string.stop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
-8
@@ -2,21 +2,32 @@ package com.zaneschepke.wireguardautotunnel.service.notification
|
|||||||
|
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.content.Context
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification.NotificationChannels
|
||||||
|
|
||||||
interface NotificationService {
|
interface NotificationService {
|
||||||
|
val context: Context
|
||||||
fun createNotification(
|
fun createNotification(
|
||||||
channelId: String,
|
channel: NotificationChannels,
|
||||||
channelName: String,
|
|
||||||
title: String = "",
|
title: String = "",
|
||||||
action: PendingIntent? = null,
|
actions: Collection<NotificationCompat.Action> = emptyList(),
|
||||||
actionText: String? = null,
|
description: String = "",
|
||||||
description: String,
|
|
||||||
showTimestamp: Boolean = false,
|
showTimestamp: Boolean = false,
|
||||||
importance: Int = NotificationManager.IMPORTANCE_HIGH,
|
importance: Int = NotificationManager.IMPORTANCE_HIGH,
|
||||||
vibration: Boolean = false,
|
|
||||||
onGoing: Boolean = true,
|
onGoing: Boolean = true,
|
||||||
lights: Boolean = true,
|
|
||||||
onlyAlertOnce: Boolean = true,
|
onlyAlertOnce: Boolean = true,
|
||||||
): Notification
|
): Notification
|
||||||
|
|
||||||
|
fun createNotificationAction(action: NotificationAction): NotificationCompat.Action
|
||||||
|
|
||||||
|
fun remove(notificationId: Int)
|
||||||
|
|
||||||
|
fun show(notificationId: Int, notification: Notification)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KERNEL_SERVICE_NOTIFICATION_ID = 123
|
||||||
|
const val AUTO_TUNNEL_NOTIFICATION_ID = 122
|
||||||
|
const val VPN_NOTIFICATION_ID = 100
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+102
-73
@@ -1,105 +1,134 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.service.notification
|
package com.zaneschepke.wireguardautotunnel.service.notification
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.MainActivity
|
import com.zaneschepke.wireguardautotunnel.receiver.NotificationActionReceiver
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class WireGuardNotification
|
class WireGuardNotification
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext override val context: Context,
|
||||||
) :
|
) : NotificationService {
|
||||||
NotificationService {
|
|
||||||
private val notificationManager =
|
|
||||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
||||||
|
|
||||||
private val watcherBuilder: NotificationCompat.Builder =
|
enum class NotificationChannels {
|
||||||
|
VPN,
|
||||||
|
AUTO_TUNNEL,
|
||||||
|
}
|
||||||
|
|
||||||
|
private val notificationManager = NotificationManagerCompat.from(context)
|
||||||
|
|
||||||
|
override fun createNotification(
|
||||||
|
channel: NotificationChannels,
|
||||||
|
title: String,
|
||||||
|
actions: Collection<NotificationCompat.Action>,
|
||||||
|
description: String,
|
||||||
|
showTimestamp: Boolean,
|
||||||
|
importance: Int,
|
||||||
|
onGoing: Boolean,
|
||||||
|
onlyAlertOnce: Boolean,
|
||||||
|
): Notification {
|
||||||
|
notificationManager.createNotificationChannel(channel.asChannel())
|
||||||
|
return channel.asBuilder().apply {
|
||||||
|
actions.forEach {
|
||||||
|
addAction(it)
|
||||||
|
}
|
||||||
|
setContentTitle(title)
|
||||||
|
setContentText(description)
|
||||||
|
setOnlyAlertOnce(onlyAlertOnce)
|
||||||
|
setOngoing(onGoing)
|
||||||
|
setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
setShowWhen(showTimestamp)
|
||||||
|
setSmallIcon(R.drawable.ic_launcher)
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createNotificationAction(notificationAction: NotificationAction): NotificationCompat.Action {
|
||||||
|
val pendingIntent = PendingIntent.getBroadcast(
|
||||||
|
context,
|
||||||
|
0,
|
||||||
|
Intent(context, NotificationActionReceiver::class.java).apply {
|
||||||
|
action = notificationAction.name
|
||||||
|
},
|
||||||
|
PendingIntent.FLAG_IMMUTABLE,
|
||||||
|
)
|
||||||
|
return NotificationCompat.Action.Builder(
|
||||||
|
R.drawable.ic_launcher,
|
||||||
|
notificationAction.title(context).uppercase(),
|
||||||
|
pendingIntent,
|
||||||
|
).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun remove(notificationId: Int) {
|
||||||
|
notificationManager.cancel(notificationId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun show(notificationId: Int, notification: Notification) {
|
||||||
|
with(notificationManager) {
|
||||||
|
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
notify(notificationId, notification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NotificationChannels.asBuilder(): NotificationCompat.Builder {
|
||||||
|
return when (this) {
|
||||||
|
NotificationChannels.VPN -> {
|
||||||
NotificationCompat.Builder(
|
NotificationCompat.Builder(
|
||||||
context,
|
context,
|
||||||
context.getString(R.string.watcher_channel_id),
|
context.getString(R.string.auto_tunnel_channel_id),
|
||||||
)
|
)
|
||||||
private val tunnelBuilder: NotificationCompat.Builder =
|
}
|
||||||
|
NotificationChannels.AUTO_TUNNEL -> {
|
||||||
NotificationCompat.Builder(
|
NotificationCompat.Builder(
|
||||||
context,
|
context,
|
||||||
context.getString(R.string.vpn_channel_id),
|
context.getString(R.string.vpn_channel_id),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun createNotification(
|
fun NotificationChannels.asChannel(): NotificationChannel {
|
||||||
channelId: String,
|
return when (this) {
|
||||||
channelName: String,
|
NotificationChannels.VPN -> {
|
||||||
title: String,
|
|
||||||
action: PendingIntent?,
|
|
||||||
actionText: String?,
|
|
||||||
description: String,
|
|
||||||
showTimestamp: Boolean,
|
|
||||||
importance: Int,
|
|
||||||
vibration: Boolean,
|
|
||||||
onGoing: Boolean,
|
|
||||||
lights: Boolean,
|
|
||||||
onlyAlertOnce: Boolean,
|
|
||||||
): Notification {
|
|
||||||
val channel =
|
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
channelId,
|
context.getString(R.string.vpn_channel_id),
|
||||||
channelName,
|
context.getString(R.string.vpn_channel_name),
|
||||||
importance,
|
NotificationManager.IMPORTANCE_HIGH,
|
||||||
)
|
).apply {
|
||||||
.let {
|
description = context.getString(R.string.vpn_channel_description)
|
||||||
it.description = title
|
enableLights(true)
|
||||||
it.enableLights(lights)
|
lightColor = Color.WHITE
|
||||||
it.lightColor = Color.RED
|
enableVibration(false)
|
||||||
it.enableVibration(vibration)
|
vibrationPattern = longArrayOf(100, 200, 300)
|
||||||
it.vibrationPattern = longArrayOf(100, 200, 300)
|
}
|
||||||
it
|
}
|
||||||
}
|
NotificationChannels.AUTO_TUNNEL -> {
|
||||||
notificationManager.createNotificationChannel(channel)
|
NotificationChannel(
|
||||||
val pendingIntent: PendingIntent =
|
context.getString(R.string.auto_tunnel_channel_id),
|
||||||
Intent(context, MainActivity::class.java).let { notificationIntent ->
|
context.getString(R.string.auto_tunnel_channel_name),
|
||||||
PendingIntent.getActivity(
|
NotificationManager.IMPORTANCE_HIGH,
|
||||||
context,
|
).apply {
|
||||||
0,
|
description = context.getString(R.string.auto_tunnel_channel_description)
|
||||||
notificationIntent,
|
enableLights(true)
|
||||||
PendingIntent.FLAG_IMMUTABLE,
|
lightColor = Color.WHITE
|
||||||
)
|
enableVibration(false)
|
||||||
}
|
vibrationPattern = longArrayOf(100, 200, 300)
|
||||||
|
|
||||||
val builder =
|
|
||||||
when (channelId) {
|
|
||||||
context.getString(R.string.watcher_channel_id) -> watcherBuilder
|
|
||||||
context.getString(R.string.vpn_channel_id) -> tunnelBuilder
|
|
||||||
else -> {
|
|
||||||
NotificationCompat.Builder(
|
|
||||||
context,
|
|
||||||
channelId,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.let {
|
|
||||||
if (action != null && actionText != null) {
|
|
||||||
it.addAction(
|
|
||||||
NotificationCompat.Action.Builder(0, actionText, action).build(),
|
|
||||||
)
|
|
||||||
it.setAutoCancel(true)
|
|
||||||
}
|
|
||||||
it.setContentTitle(title)
|
|
||||||
.setContentText(description)
|
|
||||||
.setOnlyAlertOnce(onlyAlertOnce)
|
|
||||||
.setContentIntent(pendingIntent)
|
|
||||||
.setOngoing(onGoing)
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
||||||
.setShowWhen(showTimestamp)
|
|
||||||
.setSmallIcon(R.drawable.ic_launcher)
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-34
@@ -1,24 +1,18 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.service.tile
|
package com.zaneschepke.wireguardautotunnel.service.tile
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.IBinder
|
|
||||||
import android.service.quicksettings.Tile
|
import android.service.quicksettings.Tile
|
||||||
import android.service.quicksettings.TileService
|
import android.service.quicksettings.TileService
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.LifecycleOwner
|
|
||||||
import androidx.lifecycle.LifecycleRegistry
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class AutoTunnelControlTile : TileService(), LifecycleOwner {
|
class AutoTunnelControlTile : TileService() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var appDataRepository: AppDataRepository
|
lateinit var appDataRepository: AppDataRepository
|
||||||
|
|
||||||
@@ -29,32 +23,26 @@ class AutoTunnelControlTile : TileService(), LifecycleOwner {
|
|||||||
@ApplicationScope
|
@ApplicationScope
|
||||||
lateinit var applicationScope: CoroutineScope
|
lateinit var applicationScope: CoroutineScope
|
||||||
|
|
||||||
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)
|
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
|
serviceManager.autoTunnelTile.complete(this)
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStopListening() {
|
|
||||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
serviceManager.autoTunnelTile = CompletableDeferred()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartListening() {
|
override fun onStartListening() {
|
||||||
super.onStartListening()
|
super.onStartListening()
|
||||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
|
serviceManager.autoTunnelTile.complete(this)
|
||||||
lifecycleScope.launch {
|
applicationScope.launch {
|
||||||
if (appDataRepository.tunnels.getAll().isEmpty()) return@launch setUnavailable()
|
if (appDataRepository.tunnels.getAll().isEmpty()) return@launch setUnavailable()
|
||||||
updateTileState()
|
updateTileState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateTileState() {
|
fun updateTileState() {
|
||||||
serviceManager.autoTunnelActive.value.let {
|
serviceManager.autoTunnelActive.value.let {
|
||||||
if (it) setActive() else setInactive()
|
if (it) setActive() else setInactive()
|
||||||
}
|
}
|
||||||
@@ -63,7 +51,7 @@ class AutoTunnelControlTile : TileService(), LifecycleOwner {
|
|||||||
override fun onClick() {
|
override fun onClick() {
|
||||||
super.onClick()
|
super.onClick()
|
||||||
unlockAndRun {
|
unlockAndRun {
|
||||||
lifecycleScope.launch {
|
applicationScope.launch {
|
||||||
if (serviceManager.autoTunnelActive.value) {
|
if (serviceManager.autoTunnelActive.value) {
|
||||||
serviceManager.stopAutoTunnel()
|
serviceManager.stopAutoTunnel()
|
||||||
setInactive()
|
setInactive()
|
||||||
@@ -95,18 +83,4 @@ class AutoTunnelControlTile : TileService(), LifecycleOwner {
|
|||||||
qsTile.updateTile()
|
qsTile.updateTile()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This works around an annoying unsolved frameworks bug some people are hitting. */
|
|
||||||
override fun onBind(intent: Intent): IBinder? {
|
|
||||||
var ret: IBinder? = null
|
|
||||||
try {
|
|
||||||
ret = super.onBind(intent)
|
|
||||||
} catch (_: Throwable) {
|
|
||||||
Timber.e("Failed to bind to TunnelControlTile")
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
override val lifecycle: Lifecycle
|
|
||||||
get() = lifecycleRegistry
|
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-34
@@ -1,27 +1,22 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.service.tile
|
package com.zaneschepke.wireguardautotunnel.service.tile
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
|
||||||
import android.service.quicksettings.Tile
|
import android.service.quicksettings.Tile
|
||||||
import android.service.quicksettings.TileService
|
import android.service.quicksettings.TileService
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.LifecycleOwner
|
|
||||||
import androidx.lifecycle.LifecycleRegistry
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class TunnelControlTile : TileService(), LifecycleOwner {
|
class TunnelControlTile : TileService() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var appDataRepository: AppDataRepository
|
lateinit var appDataRepository: AppDataRepository
|
||||||
|
|
||||||
@@ -32,33 +27,29 @@ class TunnelControlTile : TileService(), LifecycleOwner {
|
|||||||
@ApplicationScope
|
@ApplicationScope
|
||||||
lateinit var applicationScope: CoroutineScope
|
lateinit var applicationScope: CoroutineScope
|
||||||
|
|
||||||
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)
|
@Inject
|
||||||
|
lateinit var serviceManager: ServiceManager
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
|
serviceManager.tunnelControlTile.complete(this)
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStopListening() {
|
|
||||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
serviceManager.tunnelControlTile = CompletableDeferred()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartListening() {
|
override fun onStartListening() {
|
||||||
super.onStartListening()
|
super.onStartListening()
|
||||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
|
serviceManager.tunnelControlTile.complete(this)
|
||||||
Timber.d("Updating tile!")
|
applicationScope.launch {
|
||||||
lifecycleScope.launch {
|
|
||||||
if (appDataRepository.tunnels.getAll().isEmpty()) return@launch setUnavailable()
|
if (appDataRepository.tunnels.getAll().isEmpty()) return@launch setUnavailable()
|
||||||
updateTileState()
|
updateTileState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateTileState() {
|
fun updateTileState() = applicationScope.launch {
|
||||||
val lastActive = appDataRepository.getStartTunnelConfig()
|
val lastActive = appDataRepository.getStartTunnelConfig()
|
||||||
lastActive?.let {
|
lastActive?.let {
|
||||||
updateTile(it)
|
updateTile(it)
|
||||||
@@ -68,7 +59,7 @@ class TunnelControlTile : TileService(), LifecycleOwner {
|
|||||||
override fun onClick() {
|
override fun onClick() {
|
||||||
super.onClick()
|
super.onClick()
|
||||||
unlockAndRun {
|
unlockAndRun {
|
||||||
lifecycleScope.launch {
|
applicationScope.launch {
|
||||||
val lastActive = appDataRepository.getStartTunnelConfig()
|
val lastActive = appDataRepository.getStartTunnelConfig()
|
||||||
lastActive?.let { tunnel ->
|
lastActive?.let { tunnel ->
|
||||||
if (tunnel.isActive) {
|
if (tunnel.isActive) {
|
||||||
@@ -125,18 +116,4 @@ class TunnelControlTile : TileService(), LifecycleOwner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This works around an annoying unsolved frameworks bug some people are hitting. */
|
|
||||||
override fun onBind(intent: Intent): IBinder? {
|
|
||||||
var ret: IBinder? = null
|
|
||||||
try {
|
|
||||||
ret = super.onBind(intent)
|
|
||||||
} catch (_: Throwable) {
|
|
||||||
Timber.e("Failed to bind to TunnelControlTile")
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
override val lifecycle: Lifecycle
|
|
||||||
get() = lifecycleRegistry
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.service.tunnel
|
||||||
|
|
||||||
|
enum class BackendState {
|
||||||
|
KILL_SWITCH_ACTIVE,
|
||||||
|
SERVICE_ACTIVE,
|
||||||
|
INACTIVE,
|
||||||
|
}
|
||||||
+6
-2
@@ -12,13 +12,17 @@ interface TunnelService : Tunnel, org.amnezia.awg.backend.Tunnel {
|
|||||||
|
|
||||||
suspend fun bounceTunnel()
|
suspend fun bounceTunnel()
|
||||||
|
|
||||||
|
suspend fun getBackendState(): BackendState
|
||||||
|
|
||||||
|
suspend fun setBackendState(backendState: BackendState, allowedIps: Collection<String>)
|
||||||
|
|
||||||
val vpnState: StateFlow<VpnState>
|
val vpnState: StateFlow<VpnState>
|
||||||
|
|
||||||
suspend fun runningTunnelNames(): Set<String>
|
suspend fun runningTunnelNames(): Set<String>
|
||||||
|
|
||||||
suspend fun getState(): TunnelState
|
suspend fun getState(): TunnelState
|
||||||
|
|
||||||
fun cancelStatsJob()
|
fun cancelActiveTunnelJobs()
|
||||||
|
|
||||||
fun startStatsJob()
|
fun startActiveTunnelJobs()
|
||||||
}
|
}
|
||||||
|
|||||||
+130
-32
@@ -9,24 +9,29 @@ import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
|||||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.module.Kernel
|
import com.zaneschepke.wireguardautotunnel.module.Kernel
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.AmneziaStatistics
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.AmneziaStatistics
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.asAmBackendState
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.asBackendState
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.stateIn
|
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.amnezia.awg.backend.Tunnel
|
import org.amnezia.awg.backend.Tunnel
|
||||||
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationAction
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService.Companion.VPN_NOTIFICATION_ID
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
@@ -35,31 +40,38 @@ class WireGuardTunnel
|
|||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
private val amneziaBackend: Provider<org.amnezia.awg.backend.Backend>,
|
private val amneziaBackend: Provider<org.amnezia.awg.backend.Backend>,
|
||||||
tunnelConfigRepository: TunnelConfigRepository,
|
private val tunnelConfigRepository: TunnelConfigRepository,
|
||||||
@Kernel private val kernelBackend: Provider<Backend>,
|
@Kernel private val kernelBackend: Provider<Backend>,
|
||||||
private val appDataRepository: AppDataRepository,
|
private val appDataRepository: AppDataRepository,
|
||||||
@ApplicationScope private val applicationScope: CoroutineScope,
|
@ApplicationScope private val applicationScope: CoroutineScope,
|
||||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||||
private val serviceManager: ServiceManager,
|
private val serviceManager: ServiceManager,
|
||||||
|
private val notificationService: NotificationService,
|
||||||
) : TunnelService {
|
) : TunnelService {
|
||||||
|
|
||||||
private val _vpnState = MutableStateFlow(VpnState())
|
private val _vpnState = MutableStateFlow(VpnState())
|
||||||
override val vpnState: StateFlow<VpnState> = _vpnState.combine(
|
override val vpnState: StateFlow<VpnState> = _vpnState.asStateFlow()
|
||||||
tunnelConfigRepository.getTunnelConfigsFlow(),
|
|
||||||
) {
|
|
||||||
vpnState, tunnels ->
|
|
||||||
vpnState.copy(
|
|
||||||
tunnelConfig = tunnels.firstOrNull { it.id == vpnState.tunnelConfig?.id },
|
|
||||||
)
|
|
||||||
}.stateIn(applicationScope, SharingStarted.Eagerly, VpnState())
|
|
||||||
|
|
||||||
private var statsJob: Job? = null
|
private var statsJob: Job? = null
|
||||||
|
private var tunnelChangesJob: Job? = null
|
||||||
|
|
||||||
private val mutex = Mutex()
|
@get:Synchronized @set:Synchronized
|
||||||
|
private var isKernelBackend: Boolean? = null
|
||||||
|
|
||||||
|
private val tunnelControlMutex = Mutex()
|
||||||
|
|
||||||
|
init {
|
||||||
|
applicationScope.launch(ioDispatcher) {
|
||||||
|
appDataRepository.settings.getSettingsFlow().collect {
|
||||||
|
isKernelBackend = it.isKernelEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun backend(): Any {
|
private suspend fun backend(): Any {
|
||||||
val settings = appDataRepository.settings.getSettings()
|
val isKernelEnabled = isKernelBackend
|
||||||
if (settings.isKernelEnabled) return kernelBackend.get()
|
?: appDataRepository.settings.getSettings().isKernelEnabled
|
||||||
|
if (isKernelEnabled) return kernelBackend.get()
|
||||||
return amneziaBackend.get()
|
return amneziaBackend.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,13 +115,25 @@ constructor(
|
|||||||
override suspend fun startTunnel(tunnelConfig: TunnelConfig?, background: Boolean) {
|
override suspend fun startTunnel(tunnelConfig: TunnelConfig?, background: Boolean) {
|
||||||
if (tunnelConfig == null) return
|
if (tunnelConfig == null) return
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
mutex.withLock {
|
|
||||||
if (isTunnelAlreadyRunning(tunnelConfig)) return@withContext
|
if (isTunnelAlreadyRunning(tunnelConfig)) return@withContext
|
||||||
|
withServiceActive {
|
||||||
onBeforeStart(background)
|
onBeforeStart(background)
|
||||||
|
tunnelControlMutex.withLock {
|
||||||
setState(tunnelConfig, TunnelState.UP).onSuccess {
|
setState(tunnelConfig, TunnelState.UP).onSuccess {
|
||||||
startStatsJob()
|
startActiveTunnelJobs()
|
||||||
if (it.isUp()) appDataRepository.tunnels.save(tunnelConfig.copy(isActive = true))
|
if (it.isUp()) appDataRepository.tunnels.save(tunnelConfig.copy(isActive = true))
|
||||||
|
with(notificationService) {
|
||||||
|
val notification = createNotification(
|
||||||
|
WireGuardNotification.NotificationChannels.VPN,
|
||||||
|
title = "${context.getString(R.string.tunnel_running)} - ${tunnelConfig.name}",
|
||||||
|
actions = listOf(
|
||||||
|
notificationService.createNotificationAction(NotificationAction.TUNNEL_OFF),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
show(VPN_NOTIFICATION_ID, notification)
|
||||||
|
}
|
||||||
updateTunnelState(it, tunnelConfig)
|
updateTunnelState(it, tunnelConfig)
|
||||||
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Timber.e(it)
|
Timber.e(it)
|
||||||
}
|
}
|
||||||
@@ -119,13 +143,14 @@ constructor(
|
|||||||
|
|
||||||
override suspend fun stopTunnel() {
|
override suspend fun stopTunnel() {
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
mutex.withLock {
|
|
||||||
if (_vpnState.value.status.isDown()) return@withContext
|
if (_vpnState.value.status.isDown()) return@withContext
|
||||||
with(_vpnState.value) {
|
with(_vpnState.value) {
|
||||||
if (tunnelConfig == null) return@withContext
|
if (tunnelConfig == null) return@withContext
|
||||||
|
tunnelControlMutex.withLock {
|
||||||
setState(tunnelConfig, TunnelState.DOWN).onSuccess {
|
setState(tunnelConfig, TunnelState.DOWN).onSuccess {
|
||||||
updateTunnelState(it, null)
|
updateTunnelState(it, null)
|
||||||
onStop(tunnelConfig)
|
onStop(tunnelConfig)
|
||||||
|
notificationService.remove(VPN_NOTIFICATION_ID)
|
||||||
stopBackgroundService()
|
stopBackgroundService()
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Timber.e(it)
|
Timber.e(it)
|
||||||
@@ -135,11 +160,60 @@ constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun toggleTunnel(tunnelConfig: TunnelConfig) {
|
||||||
|
withContext(ioDispatcher) {
|
||||||
|
tunnelControlMutex.withLock {
|
||||||
|
setState(tunnelConfig, TunnelState.TOGGLE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// utility to keep vpnService alive during rapid changes to prevent bad states
|
||||||
|
private suspend fun withServiceActive(callback: suspend () -> Unit) {
|
||||||
|
when (val backend = backend()) {
|
||||||
|
is org.amnezia.awg.backend.Backend -> {
|
||||||
|
val backendState = backend.backendState
|
||||||
|
if (backendState == org.amnezia.awg.backend.Backend.BackendState.INACTIVE) {
|
||||||
|
backend.setBackendState(org.amnezia.awg.backend.Backend.BackendState.SERVICE_ACTIVE, emptyList())
|
||||||
|
}
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
is Backend -> callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun bounceTunnel() {
|
override suspend fun bounceTunnel() {
|
||||||
if (_vpnState.value.tunnelConfig == null) return
|
_vpnState.value.tunnelConfig?.let {
|
||||||
val config = _vpnState.value.tunnelConfig
|
withServiceActive {
|
||||||
stopTunnel()
|
toggleTunnel(it)
|
||||||
startTunnel(config)
|
toggleTunnel(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getBackendState(): BackendState {
|
||||||
|
return when (val backend = backend()) {
|
||||||
|
is org.amnezia.awg.backend.Backend -> {
|
||||||
|
backend.backendState.asBackendState()
|
||||||
|
}
|
||||||
|
is Backend -> BackendState.SERVICE_ACTIVE
|
||||||
|
else -> BackendState.INACTIVE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setBackendState(backendState: BackendState, allowedIps: Collection<String>) {
|
||||||
|
kotlin.runCatching {
|
||||||
|
when (val backend = backend()) {
|
||||||
|
is org.amnezia.awg.backend.Backend -> {
|
||||||
|
backend.setBackendState(backendState.asAmBackendState(), allowedIps)
|
||||||
|
}
|
||||||
|
is Backend -> {
|
||||||
|
// TODO not yet implemented
|
||||||
|
Timber.d("Kernel backend state not yet implemented")
|
||||||
|
}
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun shutDownActiveTunnel() {
|
private suspend fun shutDownActiveTunnel() {
|
||||||
@@ -152,12 +226,12 @@ constructor(
|
|||||||
|
|
||||||
private suspend fun startBackgroundService() {
|
private suspend fun startBackgroundService() {
|
||||||
serviceManager.startBackgroundService()
|
serviceManager.startBackgroundService()
|
||||||
serviceManager.requestTunnelTileUpdate()
|
serviceManager.updateTunnelTile()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stopBackgroundService() {
|
private fun stopBackgroundService() {
|
||||||
serviceManager.stopBackgroundService()
|
serviceManager.stopBackgroundService()
|
||||||
serviceManager.requestTunnelTileUpdate()
|
serviceManager.updateTunnelTile()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun onBeforeStart(background: Boolean) {
|
private suspend fun onBeforeStart(background: Boolean) {
|
||||||
@@ -169,7 +243,7 @@ constructor(
|
|||||||
|
|
||||||
private suspend fun onStop(tunnelConfig: TunnelConfig) {
|
private suspend fun onStop(tunnelConfig: TunnelConfig) {
|
||||||
appDataRepository.tunnels.save(tunnelConfig.copy(isActive = false))
|
appDataRepository.tunnels.save(tunnelConfig.copy(isActive = false))
|
||||||
cancelStatsJob()
|
cancelActiveTunnelJobs()
|
||||||
resetBackendStatistics()
|
resetBackendStatistics()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +253,13 @@ constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun emitBackendStatistics(statistics: TunnelStatistics) {
|
private fun updateTunnelConfig(tunnelConfig: TunnelConfig?) {
|
||||||
|
_vpnState.update {
|
||||||
|
it.copy(tunnelConfig = tunnelConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateBackendStatistics(statistics: TunnelStatistics) {
|
||||||
_vpnState.update {
|
_vpnState.update {
|
||||||
it.copy(statistics = statistics)
|
it.copy(statistics = statistics)
|
||||||
}
|
}
|
||||||
@@ -199,12 +279,14 @@ constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun cancelStatsJob() {
|
override fun cancelActiveTunnelJobs() {
|
||||||
statsJob?.cancel()
|
statsJob?.cancel()
|
||||||
|
tunnelChangesJob?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun startStatsJob() {
|
override fun startActiveTunnelJobs() {
|
||||||
statsJob = startTunnelStatisticsJob()
|
statsJob = startTunnelStatisticsJob()
|
||||||
|
tunnelChangesJob = startTunnelConfigChangesJob()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getName(): String {
|
override fun getName(): String {
|
||||||
@@ -216,11 +298,11 @@ constructor(
|
|||||||
delay(STATS_START_DELAY)
|
delay(STATS_START_DELAY)
|
||||||
while (true) {
|
while (true) {
|
||||||
when (backend) {
|
when (backend) {
|
||||||
is Backend -> emitBackendStatistics(
|
is Backend -> updateBackendStatistics(
|
||||||
WireGuardStatistics(backend.getStatistics(this@WireGuardTunnel)),
|
WireGuardStatistics(backend.getStatistics(this@WireGuardTunnel)),
|
||||||
)
|
)
|
||||||
is org.amnezia.awg.backend.Backend -> {
|
is org.amnezia.awg.backend.Backend -> {
|
||||||
emitBackendStatistics(
|
updateBackendStatistics(
|
||||||
AmneziaStatistics(
|
AmneziaStatistics(
|
||||||
backend.getStatistics(this@WireGuardTunnel),
|
backend.getStatistics(this@WireGuardTunnel),
|
||||||
),
|
),
|
||||||
@@ -231,18 +313,34 @@ constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun startTunnelConfigChangesJob() = applicationScope.launch(ioDispatcher) {
|
||||||
|
tunnelConfigRepository.getTunnelConfigsFlow().collect {
|
||||||
|
with(_vpnState.value) {
|
||||||
|
if (status.isDown() || tunnelConfig == null) return@collect
|
||||||
|
val vpnConfigFromStorage = it.first { it.id == tunnelConfig.id }
|
||||||
|
val isRestartNeeded = vpnConfigFromStorage.wgQuick != tunnelConfig.wgQuick ||
|
||||||
|
vpnConfigFromStorage.amQuick != tunnelConfig.amQuick
|
||||||
|
updateTunnelConfig(vpnConfigFromStorage)
|
||||||
|
if (isRestartNeeded) {
|
||||||
|
Timber.d("Bouncing tunnel on config change")
|
||||||
|
bounceTunnel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onStateChange(newState: Tunnel.State) {
|
override fun onStateChange(newState: Tunnel.State) {
|
||||||
_vpnState.update {
|
_vpnState.update {
|
||||||
it.copy(status = TunnelState.from(newState))
|
it.copy(status = TunnelState.from(newState))
|
||||||
}
|
}
|
||||||
serviceManager.requestTunnelTileUpdate()
|
serviceManager.updateTunnelTile()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStateChange(state: State) {
|
override fun onStateChange(state: State) {
|
||||||
_vpnState.update {
|
_vpnState.update {
|
||||||
it.copy(status = TunnelState.from(state))
|
it.copy(status = TunnelState.from(state))
|
||||||
}
|
}
|
||||||
serviceManager.requestTunnelTileUpdate()
|
serviceManager.updateTunnelTile()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui
|
package com.zaneschepke.wireguardautotunnel.ui
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
|
||||||
import androidx.core.os.LocaleListCompat
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.wireguard.android.backend.WgQuickBackend
|
import com.wireguard.android.backend.WgQuickBackend
|
||||||
@@ -9,16 +7,17 @@ import com.wireguard.android.util.RootShell
|
|||||||
import com.zaneschepke.logcatter.LogReader
|
import com.zaneschepke.logcatter.LogReader
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.module.AppShell
|
import com.zaneschepke.wireguardautotunnel.module.AppShell
|
||||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.BackendState
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.LocaleUtil
|
import com.zaneschepke.wireguardautotunnel.util.LocaleUtil
|
||||||
import com.zaneschepke.wireguardautotunnel.util.LocaleUtil.OPTION_PHONE_LANGUAGE
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
@@ -34,6 +33,7 @@ import kotlinx.coroutines.flow.update
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import timber.log.Timber
|
||||||
import xyz.teamgravity.pin_lock_compose.PinManager
|
import xyz.teamgravity.pin_lock_compose.PinManager
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
@@ -80,7 +80,7 @@ constructor(
|
|||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
initPin()
|
initPin()
|
||||||
initAutoTunnel()
|
initServices()
|
||||||
initTunnel()
|
initTunnel()
|
||||||
appReadyCheck()
|
appReadyCheck()
|
||||||
}
|
}
|
||||||
@@ -94,7 +94,7 @@ constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun initTunnel() {
|
private suspend fun initTunnel() {
|
||||||
if (tunnelService.get().getState() == TunnelState.UP) tunnelService.get().startStatsJob()
|
if (tunnelService.get().getState() == TunnelState.UP) tunnelService.get().startActiveTunnelJobs()
|
||||||
val activeTunnels = appDataRepository.tunnels.getActive()
|
val activeTunnels = appDataRepository.tunnels.getActive()
|
||||||
if (activeTunnels.isNotEmpty() &&
|
if (activeTunnels.isNotEmpty() &&
|
||||||
tunnelService.get().getState() == TunnelState.DOWN
|
tunnelService.get().getState() == TunnelState.DOWN
|
||||||
@@ -108,10 +108,13 @@ constructor(
|
|||||||
if (isPinEnabled) PinManager.initialize(WireGuardAutoTunnel.instance)
|
if (isPinEnabled) PinManager.initialize(WireGuardAutoTunnel.instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun initAutoTunnel() {
|
private suspend fun initServices() {
|
||||||
|
withContext(ioDispatcher) {
|
||||||
val settings = appDataRepository.settings.getSettings()
|
val settings = appDataRepository.settings.getSettings()
|
||||||
|
handleVpnKillSwitchChange(settings.isVpnKillSwitchEnabled)
|
||||||
if (settings.isAutoTunnelEnabled) serviceManager.startAutoTunnel(false)
|
if (settings.isAutoTunnelEnabled) serviceManager.startAutoTunnel(false)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onPinLockDisabled() = viewModelScope.launch(ioDispatcher) {
|
fun onPinLockDisabled() = viewModelScope.launch(ioDispatcher) {
|
||||||
PinManager.clearPin()
|
PinManager.clearPin()
|
||||||
@@ -153,11 +156,11 @@ constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onLocaleChange(localeTag: String) = viewModelScope.launch {
|
fun onLocaleChange(localeTag: String) = viewModelScope.launch {
|
||||||
val locale = LocaleUtil.getLocaleFromPrefCode(localeTag)
|
appDataRepository.appState.setLocale(localeTag)
|
||||||
val storageLocale = if (localeTag == OPTION_PHONE_LANGUAGE) OPTION_PHONE_LANGUAGE else locale
|
LocaleUtil.changeLocale(localeTag)
|
||||||
appDataRepository.appState.setLocale(storageLocale)
|
_configurationChange.update {
|
||||||
val appLocale: LocaleListCompat = LocaleListCompat.forLanguageTags(locale)
|
true
|
||||||
AppCompatDelegate.setApplicationLocales(appLocale)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onToggleRestartAtBoot() = viewModelScope.launch {
|
fun onToggleRestartAtBoot() = viewModelScope.launch {
|
||||||
@@ -170,10 +173,50 @@ constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onToggleVpnKillSwitch(enabled: Boolean) = viewModelScope.launch {
|
||||||
|
with(uiState.value.settings) {
|
||||||
|
appDataRepository.settings.save(
|
||||||
|
copy(
|
||||||
|
isVpnKillSwitchEnabled = enabled,
|
||||||
|
isLanOnKillSwitchEnabled = if (enabled) isLanOnKillSwitchEnabled else false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
handleVpnKillSwitchChange(enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun handleVpnKillSwitchChange(enabled: Boolean) {
|
||||||
|
withContext(ioDispatcher) {
|
||||||
|
if (enabled) {
|
||||||
|
Timber.d("Starting kill switch")
|
||||||
|
val allowedIps = if (appDataRepository.settings.getSettings().isLanOnKillSwitchEnabled) {
|
||||||
|
TunnelConfig.IPV4_PUBLIC_NETWORKS
|
||||||
|
} else {
|
||||||
|
emptySet()
|
||||||
|
}
|
||||||
|
tunnelService.get().setBackendState(BackendState.KILL_SWITCH_ACTIVE, allowedIps)
|
||||||
|
} else {
|
||||||
|
Timber.d("Sending shutdown of kill switch")
|
||||||
|
tunnelService.get().setBackendState(BackendState.SERVICE_ACTIVE, emptySet())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onToggleLanOnKillSwitch(enabled: Boolean) = viewModelScope.launch(ioDispatcher) {
|
||||||
|
appDataRepository.settings.save(
|
||||||
|
uiState.value.settings.copy(
|
||||||
|
isLanOnKillSwitchEnabled = enabled,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val allowedIps = if (enabled) TunnelConfig.IPV4_PUBLIC_NETWORKS else emptySet()
|
||||||
|
Timber.d("Setting allowedIps $allowedIps")
|
||||||
|
tunnelService.get().setBackendState(BackendState.KILL_SWITCH_ACTIVE, allowedIps)
|
||||||
|
}
|
||||||
|
|
||||||
fun onToggleShortcutsEnabled() = viewModelScope.launch {
|
fun onToggleShortcutsEnabled() = viewModelScope.launch {
|
||||||
with(uiState.value.settings) {
|
with(uiState.value.settings) {
|
||||||
appDataRepository.settings.save(
|
appDataRepository.settings.save(
|
||||||
this.copy(
|
copy(
|
||||||
isShortcutsEnabled = !isShortcutsEnabled,
|
isShortcutsEnabled = !isShortcutsEnabled,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.displa
|
|||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.language.LanguageScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.language.LanguageScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel.AutoTunnelScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel.AutoTunnelScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.disclosure.LocationDisclosureScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.disclosure.LocationDisclosureScreen
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.killswitch.KillSwitchScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.SupportScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.support.SupportScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.logs.LogsScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.support.logs.LogsScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
|
import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
|
||||||
@@ -144,8 +145,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) { padding ->
|
||||||
Box(modifier = Modifier.fillMaxSize().padding(it)) {
|
Box(modifier = Modifier.fillMaxSize().padding(padding)) {
|
||||||
NavHost(
|
NavHost(
|
||||||
navController,
|
navController,
|
||||||
enterTransition = { fadeIn(tween(Constants.TRANSITION_ANIMATION_TIME)) },
|
enterTransition = { fadeIn(tween(Constants.TRANSITION_ANIMATION_TIME)) },
|
||||||
@@ -207,6 +208,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
composable<Route.Scanner> {
|
composable<Route.Scanner> {
|
||||||
ScannerScreen()
|
ScannerScreen()
|
||||||
}
|
}
|
||||||
|
composable<Route.KillSwitch> {
|
||||||
|
KillSwitchScreen(appUiState, viewModel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,6 +222,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
// save battery by not polling stats while app is closed
|
// save battery by not polling stats while app is closed
|
||||||
tunnelService.cancelStatsJob()
|
tunnelService.cancelActiveTunnelJobs()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ sealed class Route {
|
|||||||
@Serializable
|
@Serializable
|
||||||
data object Display : Route()
|
data object Display : Route()
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data object KillSwitch : Route()
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data object Language : Route()
|
data object Language : Route()
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.main.components
|
package com.zaneschepke.wireguardautotunnel.ui.common.permission.vpn
|
||||||
|
|
||||||
import androidx.compose.foundation.text.ClickableText
|
import androidx.compose.foundation.text.ClickableText
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.ui.common.permission.vpn
|
||||||
|
|
||||||
|
import android.net.VpnService
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
inline fun <T> withVpnPermission(crossinline onSuccess: (t: T) -> Unit): (t: T) -> Unit {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
var showVpnPermissionDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val vpnActivity =
|
||||||
|
rememberLauncherForActivityResult(
|
||||||
|
ActivityResultContracts.StartActivityForResult(),
|
||||||
|
onResult = {
|
||||||
|
if (it.resultCode != RESULT_OK) showVpnPermissionDialog = true
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
VpnDeniedDialog(showVpnPermissionDialog, onDismiss = { showVpnPermissionDialog = false })
|
||||||
|
|
||||||
|
return {
|
||||||
|
val intent = VpnService.prepare(context)
|
||||||
|
if (intent != null) {
|
||||||
|
vpnActivity.launch(intent)
|
||||||
|
} else {
|
||||||
|
onSuccess(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.ui.common.permission
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.Settings
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.ActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isBatteryOptimizationsDisabled
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
inline fun withIgnoreBatteryOpt(ignore: Boolean, crossinline callback: () -> Unit): () -> Unit {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val batteryActivity =
|
||||||
|
rememberLauncherForActivityResult(
|
||||||
|
ActivityResultContracts.StartActivityForResult(),
|
||||||
|
) { result: ActivityResult ->
|
||||||
|
// we only ask once
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
if (ignore || context.isBatteryOptimizationsDisabled()) {
|
||||||
|
callback()
|
||||||
|
} else {
|
||||||
|
batteryActivity.launch(
|
||||||
|
Intent().apply {
|
||||||
|
action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
|
||||||
|
data = Uri.parse("package:${context.packageName}")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+32
-55
@@ -1,13 +1,7 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.main
|
package com.zaneschepke.wireguardautotunnel.ui.screens.main
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.net.VpnService
|
|
||||||
import android.provider.Settings
|
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.ActivityResult
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.gestures.ScrollableDefaults
|
import androidx.compose.foundation.gestures.ScrollableDefaults
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
@@ -41,6 +35,7 @@ import androidx.compose.ui.platform.LocalClipboardManager
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.core.os.ConfigurationCompat
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
@@ -51,18 +46,19 @@ import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
|
|||||||
import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberFileImportLauncherForResult
|
import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberFileImportLauncherForResult
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.TopNavBar
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.TopNavBar
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.permission.vpn.withVpnPermission
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.permission.withIgnoreBatteryOpt
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.AutoTunnelRowItem
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.AutoTunnelRowItem
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.GettingStartedLabel
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.GettingStartedLabel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.ScrollDismissFab
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.ScrollDismissFab
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.TunnelImportSheet
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.TunnelImportSheet
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.TunnelRowItem
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.TunnelRowItem
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.VpnDeniedDialog
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isBatteryOptimizationsDisabled
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
||||||
|
import java.text.Collator
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -73,28 +69,33 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
|||||||
val snackbar = SnackbarController.current
|
val snackbar = SnackbarController.current
|
||||||
|
|
||||||
var showBottomSheet by remember { mutableStateOf(false) }
|
var showBottomSheet by remember { mutableStateOf(false) }
|
||||||
var showVpnPermissionDialog by remember { mutableStateOf(false) }
|
|
||||||
var isFabVisible by rememberSaveable { mutableStateOf(true) }
|
var isFabVisible by rememberSaveable { mutableStateOf(true) }
|
||||||
var showDeleteTunnelAlertDialog by remember { mutableStateOf(false) }
|
var showDeleteTunnelAlertDialog by remember { mutableStateOf(false) }
|
||||||
var selectedTunnel by remember { mutableStateOf<TunnelConfig?>(null) }
|
var selectedTunnel by remember { mutableStateOf<TunnelConfig?>(null) }
|
||||||
val isRunningOnTv = remember { context.isRunningOnTv() }
|
val isRunningOnTv = remember { context.isRunningOnTv() }
|
||||||
|
|
||||||
val nestedScrollConnection = remember {
|
val currentLocale = ConfigurationCompat.getLocales(context.resources.configuration)[0]
|
||||||
NestedScrollListener({ isFabVisible = false }, { isFabVisible = true })
|
val collator = Collator.getInstance(currentLocale)
|
||||||
|
|
||||||
|
val sortedTunnels = remember(uiState.tunnels) {
|
||||||
|
uiState.tunnels.sortedWith(compareBy(collator) { it.name })
|
||||||
}
|
}
|
||||||
|
|
||||||
val vpnActivity =
|
val startAutoTunnel = withVpnPermission<Unit> { viewModel.onToggleAutoTunnel() }
|
||||||
rememberLauncherForActivityResult(
|
val startTunnel = withVpnPermission<TunnelConfig> {
|
||||||
ActivityResultContracts.StartActivityForResult(),
|
viewModel.onTunnelStart(it, uiState.settings.isKernelEnabled)
|
||||||
onResult = {
|
}
|
||||||
if (it.resultCode != RESULT_OK) showVpnPermissionDialog = true
|
val autoTunnelToggleBattery = withIgnoreBatteryOpt(uiState.generalState.isBatteryOptimizationDisableShown) {
|
||||||
},
|
if (!uiState.generalState.isBatteryOptimizationDisableShown) viewModel.setBatteryOptimizeDisableShown()
|
||||||
)
|
if (uiState.settings.isKernelEnabled) {
|
||||||
val batteryActivity =
|
viewModel.onToggleAutoTunnel()
|
||||||
rememberLauncherForActivityResult(
|
} else {
|
||||||
ActivityResultContracts.StartActivityForResult(),
|
startAutoTunnel.invoke(Unit)
|
||||||
) { result: ActivityResult ->
|
}
|
||||||
viewModel.setBatteryOptimizeDisableShown()
|
}
|
||||||
|
|
||||||
|
val nestedScrollConnection = remember {
|
||||||
|
NestedScrollListener({ isFabVisible = false }, { isFabVisible = true })
|
||||||
}
|
}
|
||||||
|
|
||||||
val tunnelFileImportResultLauncher = rememberFileImportLauncherForResult(onNoFileExplorer = {
|
val tunnelFileImportResultLauncher = rememberFileImportLauncherForResult(onNoFileExplorer = {
|
||||||
@@ -112,8 +113,6 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
|||||||
navController.navigate(Route.Scanner)
|
navController.navigate(Route.Scanner)
|
||||||
}
|
}
|
||||||
|
|
||||||
VpnDeniedDialog(showVpnPermissionDialog, onDismiss = { showVpnPermissionDialog = false })
|
|
||||||
|
|
||||||
if (showDeleteTunnelAlertDialog) {
|
if (showDeleteTunnelAlertDialog) {
|
||||||
InfoDialog(
|
InfoDialog(
|
||||||
onDismiss = { showDeleteTunnelAlertDialog = false },
|
onDismiss = { showDeleteTunnelAlertDialog = false },
|
||||||
@@ -128,35 +127,13 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestBatteryOptimizationsDisabled() {
|
|
||||||
val intent =
|
|
||||||
Intent().apply {
|
|
||||||
action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
|
|
||||||
data = Uri.parse("package:${context.packageName}")
|
|
||||||
}
|
|
||||||
batteryActivity.launch(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onAutoTunnelToggle() {
|
|
||||||
if (!uiState.generalState.isBatteryOptimizationDisableShown &&
|
|
||||||
!context.isBatteryOptimizationsDisabled() && !isRunningOnTv
|
|
||||||
) {
|
|
||||||
return requestBatteryOptimizationsDisabled()
|
|
||||||
}
|
|
||||||
val intent = if (!uiState.settings.isKernelEnabled) {
|
|
||||||
VpnService.prepare(context)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
if (intent != null) return vpnActivity.launch(intent)
|
|
||||||
viewModel.onToggleAutoTunnel()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onTunnelToggle(checked: Boolean, tunnel: TunnelConfig) {
|
fun onTunnelToggle(checked: Boolean, tunnel: TunnelConfig) {
|
||||||
val intent = if (uiState.settings.isKernelEnabled) null else VpnService.prepare(context)
|
|
||||||
if (intent != null) return vpnActivity.launch(intent)
|
|
||||||
if (!checked) viewModel.onTunnelStop().also { return }
|
if (!checked) viewModel.onTunnelStop().also { return }
|
||||||
|
if (uiState.settings.isKernelEnabled) {
|
||||||
viewModel.onTunnelStart(tunnel, uiState.settings.isKernelEnabled)
|
viewModel.onTunnelStart(tunnel, uiState.settings.isKernelEnabled)
|
||||||
|
} else {
|
||||||
|
startTunnel.invoke(tunnel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@@ -239,13 +216,13 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
item {
|
item {
|
||||||
AutoTunnelRowItem(uiState, {
|
AutoTunnelRowItem(uiState) {
|
||||||
onAutoTunnelToggle()
|
autoTunnelToggleBattery.invoke()
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items(
|
items(
|
||||||
uiState.tunnels,
|
sortedTunnels,
|
||||||
key = { tunnel -> tunnel.id },
|
key = { tunnel -> tunnel.id },
|
||||||
) { tunnel ->
|
) { tunnel ->
|
||||||
val expanded = uiState.generalState.isTunnelStatsExpanded
|
val expanded = uiState.generalState.isTunnelStatsExpanded
|
||||||
|
|||||||
+1
-12
@@ -159,18 +159,7 @@ constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onToggleAutoTunnel() = viewModelScope.launch {
|
fun onToggleAutoTunnel() = viewModelScope.launch {
|
||||||
val settings = appDataRepository.settings.getSettings()
|
serviceManager.toggleAutoTunnel(false)
|
||||||
val toggled = !settings.isAutoTunnelEnabled
|
|
||||||
if (toggled) {
|
|
||||||
serviceManager.startAutoTunnel(false)
|
|
||||||
} else {
|
|
||||||
serviceManager.stopAutoTunnel()
|
|
||||||
}
|
|
||||||
appDataRepository.settings.save(
|
|
||||||
settings.copy(
|
|
||||||
isAutoTunnelEnabled = toggled,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun saveTunnelsFromZipUri(uri: Uri, context: Context) {
|
private suspend fun saveTunnelsFromZipUri(uri: Uri, context: Context) {
|
||||||
|
|||||||
+5
-6
@@ -12,13 +12,13 @@ import androidx.compose.foundation.verticalScroll
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.ViewQuilt
|
import androidx.compose.material.icons.automirrored.outlined.ViewQuilt
|
||||||
import androidx.compose.material.icons.filled.AppShortcut
|
import androidx.compose.material.icons.filled.AppShortcut
|
||||||
import androidx.compose.material.icons.outlined.AdminPanelSettings
|
|
||||||
import androidx.compose.material.icons.outlined.Bolt
|
import androidx.compose.material.icons.outlined.Bolt
|
||||||
import androidx.compose.material.icons.outlined.Code
|
import androidx.compose.material.icons.outlined.Code
|
||||||
import androidx.compose.material.icons.outlined.FolderZip
|
import androidx.compose.material.icons.outlined.FolderZip
|
||||||
import androidx.compose.material.icons.outlined.Notifications
|
import androidx.compose.material.icons.outlined.Notifications
|
||||||
import androidx.compose.material.icons.outlined.Pin
|
import androidx.compose.material.icons.outlined.Pin
|
||||||
import androidx.compose.material.icons.outlined.Restore
|
import androidx.compose.material.icons.outlined.Restore
|
||||||
|
import androidx.compose.material.icons.outlined.VpnKeyOff
|
||||||
import androidx.compose.material.icons.outlined.VpnLock
|
import androidx.compose.material.icons.outlined.VpnLock
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@@ -49,7 +49,6 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.Forwar
|
|||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.topPadding
|
import com.zaneschepke.wireguardautotunnel.ui.theme.topPadding
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.launchNotificationSettings
|
import com.zaneschepke.wireguardautotunnel.util.extensions.launchNotificationSettings
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.launchVpnSettings
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.showToast
|
import com.zaneschepke.wireguardautotunnel.util.extensions.showToast
|
||||||
@@ -179,18 +178,18 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
|||||||
onClick = { appViewModel.onToggleAlwaysOnVPN() },
|
onClick = { appViewModel.onToggleAlwaysOnVPN() },
|
||||||
),
|
),
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
Icons.Outlined.AdminPanelSettings,
|
Icons.Outlined.VpnKeyOff,
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
stringResource(R.string.kill_switch),
|
stringResource(R.string.kill_switch_options),
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
context.launchVpnSettings()
|
navController.navigate(Route.KillSwitch)
|
||||||
},
|
},
|
||||||
trailing = {
|
trailing = {
|
||||||
ForwardButton { context.launchVpnSettings() }
|
ForwardButton { navController.navigate(Route.KillSwitch) }
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
+1
-4
@@ -2,11 +2,8 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.langu
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
@@ -53,7 +50,7 @@ fun LanguageScreen(appUiState: AppUiState, appViewModel: AppViewModel) {
|
|||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize().padding(padding)
|
.fillMaxSize().padding(padding)
|
||||||
.padding(horizontal = 24.dp.scaledWidth()).windowInsetsPadding(WindowInsets.navigationBars),
|
.padding(horizontal = 24.dp.scaledWidth()),
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
Box(modifier = Modifier.padding(top = 24.dp.scaledHeight())) {
|
Box(modifier = Modifier.padding(top = 24.dp.scaledHeight())) {
|
||||||
|
|||||||
+3
-2
@@ -6,11 +6,12 @@ import androidx.compose.foundation.layout.Arrangement
|
|||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.AirplanemodeActive
|
import androidx.compose.material.icons.outlined.AirplanemodeActive
|
||||||
import androidx.compose.material.icons.outlined.Code
|
import androidx.compose.material.icons.outlined.Code
|
||||||
@@ -116,7 +117,6 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
|||||||
)
|
)
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
contentWindowInsets = WindowInsets(0.dp),
|
|
||||||
topBar = {
|
topBar = {
|
||||||
TopNavBar(stringResource(R.string.auto_tunneling))
|
TopNavBar(stringResource(R.string.auto_tunneling))
|
||||||
},
|
},
|
||||||
@@ -128,6 +128,7 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
|||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(padding)
|
.padding(padding)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
.padding(top = 24.dp.scaledHeight())
|
.padding(top = 24.dp.scaledHeight())
|
||||||
.padding(horizontal = 24.dp.scaledWidth()),
|
.padding(horizontal = 24.dp.scaledWidth()),
|
||||||
) {
|
) {
|
||||||
|
|||||||
+142
@@ -0,0 +1,142 @@
|
|||||||
|
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.killswitch
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.AdminPanelSettings
|
||||||
|
import androidx.compose.material.icons.outlined.Lan
|
||||||
|
import androidx.compose.material.icons.outlined.VpnKey
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
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.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.AppUiState
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.TopNavBar
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.permission.vpn.withVpnPermission
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.ForwardButton
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.launchVpnSettings
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun KillSwitchScreen(uiState: AppUiState, appViewModel: AppViewModel) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val toggleVpnSwitch = withVpnPermission<Boolean> { appViewModel.onToggleVpnKillSwitch(it) }
|
||||||
|
|
||||||
|
fun toggleVpnKillSwitch() {
|
||||||
|
with(uiState.settings) {
|
||||||
|
if (isVpnKillSwitchEnabled) {
|
||||||
|
appViewModel.onToggleVpnKillSwitch(false)
|
||||||
|
} else {
|
||||||
|
toggleVpnSwitch.invoke(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleLanOnKillSwitch() {
|
||||||
|
with(uiState.settings) {
|
||||||
|
appViewModel.onToggleLanOnKillSwitch(!isLanOnKillSwitchEnabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopNavBar(stringResource(R.string.kill_switch))
|
||||||
|
},
|
||||||
|
) { padding ->
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.Start,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(24.dp.scaledHeight(), Alignment.Top),
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize().padding(padding)
|
||||||
|
.padding(top = 24.dp.scaledHeight())
|
||||||
|
.padding(horizontal = 24.dp.scaledWidth()),
|
||||||
|
) {
|
||||||
|
SurfaceSelectionGroupButton(
|
||||||
|
listOf(
|
||||||
|
SelectionItem(
|
||||||
|
Icons.Outlined.AdminPanelSettings,
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.native_kill_switch),
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = { context.launchVpnSettings() },
|
||||||
|
trailing = {
|
||||||
|
ForwardButton { context.launchVpnSettings() }
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
SurfaceSelectionGroupButton(
|
||||||
|
buildList {
|
||||||
|
add(
|
||||||
|
SelectionItem(
|
||||||
|
Icons.Outlined.VpnKey,
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.vpn_kill_switch),
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
toggleVpnKillSwitch()
|
||||||
|
},
|
||||||
|
trailing = {
|
||||||
|
ScaledSwitch(
|
||||||
|
uiState.settings.isVpnKillSwitchEnabled,
|
||||||
|
onClick = {
|
||||||
|
toggleVpnKillSwitch()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if (uiState.settings.isVpnKillSwitchEnabled) {
|
||||||
|
add(
|
||||||
|
SelectionItem(
|
||||||
|
Icons.Outlined.Lan,
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.allow_lan_traffic),
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = { toggleLanOnKillSwitch() },
|
||||||
|
description = {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.bypass_lan_for_kill_switch),
|
||||||
|
style = MaterialTheme.typography.bodySmall.copy(MaterialTheme.colorScheme.outline),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
trailing = {
|
||||||
|
ScaledSwitch(
|
||||||
|
uiState.settings.isLanOnKillSwitchEnabled,
|
||||||
|
onClick = {
|
||||||
|
toggleLanOnKillSwitch()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,30 +1,20 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.util
|
package com.zaneschepke.wireguardautotunnel.util
|
||||||
|
|
||||||
import android.content.res.Resources
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.core.os.ConfigurationCompat
|
import androidx.core.os.LocaleListCompat
|
||||||
import com.zaneschepke.wireguardautotunnel.BuildConfig
|
import com.zaneschepke.wireguardautotunnel.BuildConfig
|
||||||
|
|
||||||
object LocaleUtil {
|
object LocaleUtil {
|
||||||
private const val DEFAULT_LANG = "en"
|
|
||||||
val supportedLocales: Array<String> = BuildConfig.LANGUAGES
|
val supportedLocales: Array<String> = BuildConfig.LANGUAGES
|
||||||
const val OPTION_PHONE_LANGUAGE = "sys_def"
|
const val OPTION_PHONE_LANGUAGE = "sys_def"
|
||||||
|
|
||||||
/**
|
fun changeLocale(locale: String) {
|
||||||
* returns the locale to use depending on the preference value
|
if (locale == OPTION_PHONE_LANGUAGE) return resetToSystemLanguage()
|
||||||
* when preference value = "sys_def" returns the locale of current system
|
val appLocale: LocaleListCompat = LocaleListCompat.forLanguageTags(locale)
|
||||||
* else it returns the locale code e.g. "en", "bn" etc.
|
AppCompatDelegate.setApplicationLocales(appLocale)
|
||||||
*/
|
}
|
||||||
fun getLocaleFromPrefCode(prefCode: String): String {
|
|
||||||
val localeCode = if (prefCode != OPTION_PHONE_LANGUAGE) {
|
private fun resetToSystemLanguage() {
|
||||||
prefCode
|
AppCompatDelegate.setApplicationLocales(LocaleListCompat.getEmptyLocaleList())
|
||||||
} else {
|
|
||||||
val systemLang = ConfigurationCompat.getLocales(Resources.getSystem().configuration).get(0)?.language ?: DEFAULT_LANG
|
|
||||||
if (systemLang in supportedLocales) {
|
|
||||||
systemLang
|
|
||||||
} else {
|
|
||||||
DEFAULT_LANG
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return localeCode
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
@@ -3,12 +3,14 @@ package com.zaneschepke.wireguardautotunnel.util.extensions
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import com.wireguard.android.util.RootShell
|
import com.wireguard.android.util.RootShell
|
||||||
import com.wireguard.config.Peer
|
import com.wireguard.config.Peer
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.BackendState
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.SilverTree
|
import com.zaneschepke.wireguardautotunnel.ui.theme.SilverTree
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.Straw
|
import com.zaneschepke.wireguardautotunnel.ui.theme.Straw
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
||||||
|
import org.amnezia.awg.backend.Backend
|
||||||
import org.amnezia.awg.config.Config
|
import org.amnezia.awg.config.Config
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
@@ -85,3 +87,11 @@ fun RootShell.getCurrentWifiName(): String? {
|
|||||||
this.run(response, "dumpsys wifi | grep -o \"SSID: [^,]*\" | cut -d ' ' -f2- | tr -d '\"'")
|
this.run(response, "dumpsys wifi | grep -o \"SSID: [^,]*\" | cut -d ' ' -f2- | tr -d '\"'")
|
||||||
return response.lastOrNull()
|
return response.lastOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Backend.BackendState.asBackendState(): BackendState {
|
||||||
|
return BackendState.valueOf(this.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BackendState.asAmBackendState(): Backend.BackendState {
|
||||||
|
return Backend.BackendState.valueOf(this.name)
|
||||||
|
}
|
||||||
|
|||||||
@@ -81,7 +81,6 @@
|
|||||||
<string name="version">Version</string>
|
<string name="version">Version</string>
|
||||||
<string name="settings">Einstellungen</string>
|
<string name="settings">Einstellungen</string>
|
||||||
<string name="support">Unterstützung</string>
|
<string name="support">Unterstützung</string>
|
||||||
<string name="watcher_channel_id">Wächterkanal</string>
|
|
||||||
<string name="error_authentication_failed">Authentifizierung fehlgeschlagen</string>
|
<string name="error_authentication_failed">Authentifizierung fehlgeschlagen</string>
|
||||||
<string name="export_configs">Konfigurationen exportieren</string>
|
<string name="export_configs">Konfigurationen exportieren</string>
|
||||||
<string name="unknown_error">Unbekannter Fehler aufgetreten</string>
|
<string name="unknown_error">Unbekannter Fehler aufgetreten</string>
|
||||||
@@ -99,7 +98,6 @@
|
|||||||
<string name="set_primary_tunnel">Als Primären Tunnel setzen</string>
|
<string name="set_primary_tunnel">Als Primären Tunnel setzen</string>
|
||||||
<string name="vpn_channel_id">VPN Kanal</string>
|
<string name="vpn_channel_id">VPN Kanal</string>
|
||||||
<string name="vpn_channel_name">VPN Benachrichtigungskanal</string>
|
<string name="vpn_channel_name">VPN Benachrichtigungskanal</string>
|
||||||
<string name="watcher_channel_name">Wächterbenachrichtigungskanal</string>
|
|
||||||
<string name="turn_off_tunnel">Aktion erfordert deaktivierten Tunnel</string>
|
<string name="turn_off_tunnel">Aktion erfordert deaktivierten Tunnel</string>
|
||||||
<string name="kernel">Kernel</string>
|
<string name="kernel">Kernel</string>
|
||||||
<string name="use_kernel">Kernelmodul verwenden</string>
|
<string name="use_kernel">Kernelmodul verwenden</string>
|
||||||
|
|||||||
@@ -101,8 +101,6 @@
|
|||||||
<string name="app_name">WG Tunnel</string>
|
<string name="app_name">WG Tunnel</string>
|
||||||
<string name="vpn_channel_id">Canal VPN</string>
|
<string name="vpn_channel_id">Canal VPN</string>
|
||||||
<string name="vpn_channel_name">Canal de notificación VPN</string>
|
<string name="vpn_channel_name">Canal de notificación VPN</string>
|
||||||
<string name="watcher_channel_id">Canal del obvervador</string>
|
|
||||||
<string name="watcher_channel_name">Canal de notificación del obvervador</string>
|
|
||||||
<string name="prominent_background_location_message">La monitorización SSID Wi-Fi necesita de permiso de ubicación en segundo plano incluso si la app está cerrada. Mira el enlace a la Política de Privacidad en la pantalla de ayuda para más detalles.</string>
|
<string name="prominent_background_location_message">La monitorización SSID Wi-Fi necesita de permiso de ubicación en segundo plano incluso si la app está cerrada. Mira el enlace a la Política de Privacidad en la pantalla de ayuda para más detalles.</string>
|
||||||
<string name="junk_packet_count">Recuento de paquetes basura</string>
|
<string name="junk_packet_count">Recuento de paquetes basura</string>
|
||||||
<string name="junk_packet_minimum_size">Tamaño mínimo del paquete basura</string>
|
<string name="junk_packet_minimum_size">Tamaño mínimo del paquete basura</string>
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="vpn_channel_id">Canal VPN</string>
|
<string name="vpn_channel_id">Canal VPN</string>
|
||||||
<string name="watcher_channel_id">Canal de surveillance</string>
|
|
||||||
<string name="watcher_channel_name">Canal de notification de surveillance</string>
|
|
||||||
<string name="turn_off_tunnel">Cette action nécessite la désactivation du tunnel</string>
|
<string name="turn_off_tunnel">Cette action nécessite la désactivation du tunnel</string>
|
||||||
<string name="no_tunnels">Aucun tunnel n\'a été ajouté pour le moment !</string>
|
<string name="no_tunnels">Aucun tunnel n\'a été ajouté pour le moment !</string>
|
||||||
<string name="tunnels">Tunnels</string>
|
<string name="tunnels">Tunnels</string>
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
<string name="kernel">Kernel</string>
|
<string name="kernel">Kernel</string>
|
||||||
<string name="init_packet_junk_size">Ukuran sampah paket init</string>
|
<string name="init_packet_junk_size">Ukuran sampah paket init</string>
|
||||||
<string name="app_name">WG Tunnel</string>
|
<string name="app_name">WG Tunnel</string>
|
||||||
<string name="watcher_channel_name">Notifikasi Saluran Pengamat</string>
|
|
||||||
<string name="error_file_extension">File bukan .conf atau .zip</string>
|
<string name="error_file_extension">File bukan .conf atau .zip</string>
|
||||||
<string name="turn_off_tunnel">Aksi memerlukan tunnel mati</string>
|
<string name="turn_off_tunnel">Aksi memerlukan tunnel mati</string>
|
||||||
<string name="no_tunnels">Belum ada tunnel yang ditambahkan!</string>
|
<string name="no_tunnels">Belum ada tunnel yang ditambahkan!</string>
|
||||||
@@ -99,7 +98,6 @@
|
|||||||
<string name="handshake">handshake</string>
|
<string name="handshake">handshake</string>
|
||||||
<string name="vpn_channel_id">Saluran VPN</string>
|
<string name="vpn_channel_id">Saluran VPN</string>
|
||||||
<string name="vpn_channel_name">Notifikasi Saluran VPN</string>
|
<string name="vpn_channel_name">Notifikasi Saluran VPN</string>
|
||||||
<string name="watcher_channel_id">Saluran Pengamat</string>
|
|
||||||
<string name="prominent_background_location_message">Fitur ini memerlukan izin lokasi latar belakang untuk mengaktifkan pemantauan SSID Wi-Fi bahkan saat aplikasi ditutup. Untuk detail lebih lanjut, silakan lihat Kebijakan Privasi yang ditautkan di layar Dukungan.</string>
|
<string name="prominent_background_location_message">Fitur ini memerlukan izin lokasi latar belakang untuk mengaktifkan pemantauan SSID Wi-Fi bahkan saat aplikasi ditutup. Untuk detail lebih lanjut, silakan lihat Kebijakan Privasi yang ditautkan di layar Dukungan.</string>
|
||||||
<string name="copy_public_key">Salin kunci publik</string>
|
<string name="copy_public_key">Salin kunci publik</string>
|
||||||
<string name="base64_key">kunci base64</string>
|
<string name="base64_key">kunci base64</string>
|
||||||
|
|||||||
@@ -15,8 +15,6 @@
|
|||||||
<string name="icon">Icoon</string>
|
<string name="icon">Icoon</string>
|
||||||
<string name="include">Meenemen</string>
|
<string name="include">Meenemen</string>
|
||||||
<string name="addresses">Adres</string>
|
<string name="addresses">Adres</string>
|
||||||
<string name="watcher_channel_id">Watcher Kanaal</string>
|
|
||||||
<string name="watcher_channel_name">Watcher Notificatiekanaal</string>
|
|
||||||
<string name="peer">Peer (extern systeem)</string>
|
<string name="peer">Peer (extern systeem)</string>
|
||||||
<string name="allowed_ips">Allowed IPs</string>
|
<string name="allowed_ips">Allowed IPs</string>
|
||||||
<string name="always_on_vpn_support">Altijd-aan VPN toestaan</string>
|
<string name="always_on_vpn_support">Altijd-aan VPN toestaan</string>
|
||||||
|
|||||||
@@ -99,8 +99,6 @@
|
|||||||
<string name="app_name">WG Tunnel</string>
|
<string name="app_name">WG Tunnel</string>
|
||||||
<string name="vpn_channel_id">Canal de VPN</string>
|
<string name="vpn_channel_id">Canal de VPN</string>
|
||||||
<string name="vpn_channel_name">Canal de notificações VPN</string>
|
<string name="vpn_channel_name">Canal de notificações VPN</string>
|
||||||
<string name="watcher_channel_id">Canal de vigia</string>
|
|
||||||
<string name="watcher_channel_name">Canal de notificações de vigia</string>
|
|
||||||
<string name="prominent_background_location_message">Este recurso precisa de permissões de localização em segundo plano para ativar o monitoramento do SSID da rede Wi-Fi mesmo quando a aplicação está fechado. Para mais pormenores, por favor veja a Política de Privacidade no ecrã de Suporte.</string>
|
<string name="prominent_background_location_message">Este recurso precisa de permissões de localização em segundo plano para ativar o monitoramento do SSID da rede Wi-Fi mesmo quando a aplicação está fechado. Para mais pormenores, por favor veja a Política de Privacidade no ecrã de Suporte.</string>
|
||||||
<string name="trusted_ssid_value_description">Envie o SSID</string>
|
<string name="trusted_ssid_value_description">Envie o SSID</string>
|
||||||
<string name="add_tunnels_text">Adicionar a partir de ficheiro ou zip</string>
|
<string name="add_tunnels_text">Adicionar a partir de ficheiro ou zip</string>
|
||||||
|
|||||||
@@ -110,8 +110,6 @@
|
|||||||
<string name="error_file_format">Formato de configuração inválido</string>
|
<string name="error_file_format">Formato de configuração inválido</string>
|
||||||
<string name="vpn_channel_id">Canal de VPN</string>
|
<string name="vpn_channel_id">Canal de VPN</string>
|
||||||
<string name="vpn_channel_name">Canal de notificações VPN</string>
|
<string name="vpn_channel_name">Canal de notificações VPN</string>
|
||||||
<string name="watcher_channel_id">Canal de vigia</string>
|
|
||||||
<string name="watcher_channel_name">Canal de notificações de vigia</string>
|
|
||||||
<string name="db_name">wg-tunnel-db</string>
|
<string name="db_name">wg-tunnel-db</string>
|
||||||
<string name="set_custom_ping_ip">Definir ip ping personalizado</string>
|
<string name="set_custom_ping_ip">Definir ip ping personalizado</string>
|
||||||
<string name="vpn_denied_dialog_title">Permissão negada</string>
|
<string name="vpn_denied_dialog_title">Permissão negada</string>
|
||||||
|
|||||||
@@ -63,8 +63,6 @@
|
|||||||
<string name="app_name">WG Tunnel</string>
|
<string name="app_name">WG Tunnel</string>
|
||||||
<string name="vpn_channel_id">Канал VPN</string>
|
<string name="vpn_channel_id">Канал VPN</string>
|
||||||
<string name="vpn_channel_name">Канал уведомлений VPN</string>
|
<string name="vpn_channel_name">Канал уведомлений VPN</string>
|
||||||
<string name="watcher_channel_id">Канал наблюдателя</string>
|
|
||||||
<string name="watcher_channel_name">Канал уведомлений наблюдателя</string>
|
|
||||||
<string name="tunnels">Туннели</string>
|
<string name="tunnels">Туннели</string>
|
||||||
<string name="okay">Хорошо</string>
|
<string name="okay">Хорошо</string>
|
||||||
<string name="prominent_background_location_title">Фоновая передача местоположения</string>
|
<string name="prominent_background_location_title">Фоновая передача местоположения</string>
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
<string name="app_name">WG Tunnel</string>
|
<string name="app_name">WG Tunnel</string>
|
||||||
<string name="vpn_channel_id">VPN Kanalı</string>
|
<string name="vpn_channel_id">VPN Kanalı</string>
|
||||||
<string name="vpn_channel_name">VPN Bildirim Kanalı</string>
|
<string name="vpn_channel_name">VPN Bildirim Kanalı</string>
|
||||||
<string name="watcher_channel_id">İzleyici Kanalı</string>
|
|
||||||
<string name="watcher_channel_name">İzleyici Bildirim Kanalı</string>
|
|
||||||
<string name="github_url" translatable="false">https://github.com/zaneschepke/wgtunnel/issues</string>
|
<string name="github_url" translatable="false">https://github.com/zaneschepke/wgtunnel/issues</string>
|
||||||
<string name="docs_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/overview.html</string>
|
<string name="docs_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/overview.html</string>
|
||||||
<string name="privacy_policy_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/privacypolicy.html</string>
|
<string name="privacy_policy_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/privacypolicy.html</string>
|
||||||
|
|||||||
@@ -97,12 +97,10 @@
|
|||||||
<string name="no_browser_detected">没有安装浏览器</string>
|
<string name="no_browser_detected">没有安装浏览器</string>
|
||||||
<string name="incorrect_pin">密码不正确</string>
|
<string name="incorrect_pin">密码不正确</string>
|
||||||
<string name="set_custom_ping_ip">自定义 Ping 的目标 ip</string>
|
<string name="set_custom_ping_ip">自定义 Ping 的目标 ip</string>
|
||||||
<string name="watcher_channel_name">守护者通知频道</string>
|
|
||||||
<string name="vpn_channel_id">VPN 频道</string>
|
<string name="vpn_channel_id">VPN 频道</string>
|
||||||
<string name="junk_packet_count">无效包计数</string>
|
<string name="junk_packet_count">无效包计数</string>
|
||||||
<string name="app_name">WG Tunnel</string>
|
<string name="app_name">WG Tunnel</string>
|
||||||
<string name="vpn_channel_name">VPN 通知频道</string>
|
<string name="vpn_channel_name">VPN 通知频道</string>
|
||||||
<string name="watcher_channel_id">守护者频道</string>
|
|
||||||
<string name="open_issue">查看问题</string>
|
<string name="open_issue">查看问题</string>
|
||||||
<string name="read_logs">查看日志</string>
|
<string name="read_logs">查看日志</string>
|
||||||
<string name="auto">(自动)</string>
|
<string name="auto">(自动)</string>
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
<string name="app_name">WG Tunnel</string>
|
<string name="app_name">WG Tunnel</string>
|
||||||
<string name="vpn_channel_id">VPN Channel</string>
|
<string name="vpn_channel_id">VPN Channel</string>
|
||||||
<string name="vpn_channel_name">VPN Notification Channel</string>
|
<string name="vpn_channel_name">VPN Notification Channel</string>
|
||||||
<string name="watcher_channel_id">Watcher Channel</string>
|
|
||||||
<string name="watcher_channel_name">Watcher Notification Channel</string>
|
|
||||||
<string name="github_url" translatable="false">https://github.com/zaneschepke/wgtunnel/issues</string>
|
<string name="github_url" translatable="false">https://github.com/zaneschepke/wgtunnel/issues</string>
|
||||||
<string name="docs_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/overview.html</string>
|
<string name="docs_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/overview.html</string>
|
||||||
<string name="privacy_policy_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/privacypolicy.html</string>
|
<string name="privacy_policy_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/privacypolicy.html</string>
|
||||||
@@ -82,7 +80,7 @@
|
|||||||
<string name="error_no_file_explorer">No file explorer installed</string>
|
<string name="error_no_file_explorer">No file explorer installed</string>
|
||||||
<string name="error_invalid_code">Invalid QR code</string>
|
<string name="error_invalid_code">Invalid QR code</string>
|
||||||
<string name="location_services_missing_message">The app is not detecting any location services enabled on your device. Depending on the device, this could cause the untrusted wifi feature to fail to read the wifi name. Would you like to continue anyways?</string>
|
<string name="location_services_missing_message">The app is not detecting any location services enabled on your device. Depending on the device, this could cause the untrusted wifi feature to fail to read the wifi name. Would you like to continue anyways?</string>
|
||||||
<string name="auto_tunnel_title">Auto-tunnel Service</string>
|
<string name="auto_tunnel_title">Auto-tunnel service</string>
|
||||||
<string name="delete_tunnel">Delete tunnel</string>
|
<string name="delete_tunnel">Delete tunnel</string>
|
||||||
<string name="delete_tunnel_message">Are you sure you would like to delete this tunnel?</string>
|
<string name="delete_tunnel_message">Are you sure you would like to delete this tunnel?</string>
|
||||||
<string name="yes">Yes</string>
|
<string name="yes">Yes</string>
|
||||||
@@ -182,4 +180,14 @@
|
|||||||
<string name="stop_on_internet_loss">Stop tunnel on internet loss</string>
|
<string name="stop_on_internet_loss">Stop tunnel on internet loss</string>
|
||||||
<string name="ethernet_tunnel">Ethernet tunnel</string>
|
<string name="ethernet_tunnel">Ethernet tunnel</string>
|
||||||
<string name="set_ethernet_tunnel">Set as ethernet tunnel</string>
|
<string name="set_ethernet_tunnel">Set as ethernet tunnel</string>
|
||||||
|
<string name="native_kill_switch">Native kill switch</string>
|
||||||
|
<string name="vpn_kill_switch">VPN kill switch</string>
|
||||||
|
<string name="kill_switch_options">Kill switch options</string>
|
||||||
|
<string name="allow_lan_traffic">Allow LAN traffic</string>
|
||||||
|
<string name="bypass_lan_for_kill_switch">Bypass LAN for kill switch</string>
|
||||||
|
<string name="vpn_channel_description">A channel for VPN state notifications</string>
|
||||||
|
<string name="auto_tunnel_channel_id">Auto-tunnel Channel</string>
|
||||||
|
<string name="auto_tunnel_channel_name">Auto-tunnel Notification Channel</string>
|
||||||
|
<string name="auto_tunnel_channel_description">A channel for auto-tunnel state notifications</string>
|
||||||
|
<string name="stop">stop</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[versions]
|
[versions]
|
||||||
accompanist = "0.36.0"
|
accompanist = "0.36.0"
|
||||||
activityCompose = "1.9.3"
|
activityCompose = "1.9.3"
|
||||||
amneziawgAndroid = "1.2.2"
|
amneziawgAndroid = "1.2.3"
|
||||||
androidx-junit = "1.2.1"
|
androidx-junit = "1.2.1"
|
||||||
appcompat = "1.7.0"
|
appcompat = "1.7.0"
|
||||||
biometricKtx = "1.2.0-alpha05"
|
biometricKtx = "1.2.0-alpha05"
|
||||||
@@ -10,7 +10,7 @@ coreKtx = "1.15.0"
|
|||||||
datastorePreferences = "1.1.1"
|
datastorePreferences = "1.1.1"
|
||||||
desugar_jdk_libs = "2.1.3"
|
desugar_jdk_libs = "2.1.3"
|
||||||
espressoCore = "3.6.1"
|
espressoCore = "3.6.1"
|
||||||
hiltAndroid = "2.52"
|
hiltAndroid = "2.53"
|
||||||
hiltNavigationCompose = "1.2.0"
|
hiltNavigationCompose = "1.2.0"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
kotlinx-serialization-json = "1.7.3"
|
kotlinx-serialization-json = "1.7.3"
|
||||||
@@ -21,9 +21,9 @@ pinLockCompose = "1.0.4"
|
|||||||
roomVersion = "2.6.1"
|
roomVersion = "2.6.1"
|
||||||
timber = "5.0.1"
|
timber = "5.0.1"
|
||||||
tunnel = "1.2.1"
|
tunnel = "1.2.1"
|
||||||
androidGradlePlugin = "8.7.3"
|
androidGradlePlugin = "8.8.0-rc01"
|
||||||
kotlin = "2.0.21"
|
kotlin = "2.1.0"
|
||||||
ksp = "2.0.21-1.0.28"
|
ksp = "2.1.0-1.0.29"
|
||||||
composeBom = "2024.11.00"
|
composeBom = "2024.11.00"
|
||||||
compose = "1.7.5"
|
compose = "1.7.5"
|
||||||
zxingAndroidEmbedded = "4.3.0"
|
zxingAndroidEmbedded = "4.3.0"
|
||||||
@@ -32,7 +32,7 @@ gradlePlugins-grgit = "5.3.0"
|
|||||||
|
|
||||||
#plugins
|
#plugins
|
||||||
material = "1.12.0"
|
material = "1.12.0"
|
||||||
gradlePlugins-ktlint="12.1.1"
|
gradlePlugins-ktlint="12.1.2"
|
||||||
|
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
|||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
#Wed Oct 11 22:39:21 EDT 2023
|
#Wed Oct 11 22:39:21 EDT 2023
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
|
||||||
distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
|
distributionSha256Sum=31c55713e40233a8303827ceb42ca48a47267a0ad4bab9177123121e71524c26
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
11
|
12
|
||||||
Reference in New Issue
Block a user