mirror of
https://github.com/wgtunnel/android.git
synced 2026-07-03 14:07:49 +02:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c71c4e5b29 | |||
| 7f0fea3766 |
@@ -1,6 +1,8 @@
|
||||
package com.zaneschepke.wireguardautotunnel
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
@@ -32,12 +34,14 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.toRoute
|
||||
import com.zaneschepke.networkmonitor.NetworkMonitor
|
||||
import com.zaneschepke.wireguardautotunnel.core.shortcut.ShortcutManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppStateRepository
|
||||
@@ -68,6 +72,7 @@ import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@@ -83,6 +88,11 @@ class MainActivity : AppCompatActivity() {
|
||||
@Inject
|
||||
lateinit var shortcutManager: ShortcutManager
|
||||
|
||||
@Inject
|
||||
lateinit var networkMonitor: NetworkMonitor
|
||||
|
||||
private var lastLocationPermissionState: Boolean? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
enableEdgeToEdge(
|
||||
statusBarStyle = SystemBarStyle.Companion.auto(Color.TRANSPARENT, Color.TRANSPARENT),
|
||||
@@ -256,4 +266,22 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
checkPermissionAndNotify()
|
||||
}
|
||||
|
||||
private fun checkPermissionAndNotify() {
|
||||
val hasLocation = ContextCompat.checkSelfPermission(
|
||||
this,
|
||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
if (lastLocationPermissionState != hasLocation) {
|
||||
Timber.d("Location permission changed to: $hasLocation")
|
||||
if (hasLocation) {
|
||||
networkMonitor.sendLocationPermissionsGrantedBroadcast()
|
||||
}
|
||||
lastLocationPermissionState = hasLocation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,9 +56,6 @@ class ServiceManager @Inject constructor(
|
||||
runCatching {
|
||||
autoTunnelService = CompletableDeferred()
|
||||
startService(AutoTunnelService::class.java, background)
|
||||
val service = withTimeoutOrNull(SERVICE_START_TIMEOUT) { autoTunnelService.await() }
|
||||
?: throw IllegalStateException("AutoTunnelService start timed out")
|
||||
service.start()
|
||||
_autoTunnelActive.update { true }
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
|
||||
+5
-3
@@ -94,9 +94,11 @@ class AutoTunnelService : LifecycleService() {
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
Timber.d("onStartCommand executed with startId: $startId")
|
||||
serviceManager.autoTunnelService.complete(this)
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
start()
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
fun start() {
|
||||
@@ -178,8 +180,8 @@ class AutoTunnelService : LifecycleService() {
|
||||
combineSettings(),
|
||||
appDataRepository.get().settings.flow
|
||||
.distinctUntilChanged { old, new -> old.isKernelEnabled == new.isKernelEnabled } // Only emit when isKernelEnabled changes
|
||||
.flatMapLatest { settings ->
|
||||
networkMonitor.getNetworkStatusFlow(true, settings.isKernelEnabled)
|
||||
.flatMapLatest {
|
||||
networkMonitor.networkStatusFlow
|
||||
.flowOn(ioDispatcher)
|
||||
.map { buildNetworkState(it) }
|
||||
}
|
||||
|
||||
@@ -228,7 +228,7 @@ abstract class BaseTunnel(
|
||||
}
|
||||
|
||||
private suspend fun monitorNetworkStatus() {
|
||||
networkMonitor.getNetworkStatusFlow(includeWifiSsid = false, useRootShell = false)
|
||||
networkMonitor.networkStatusFlow
|
||||
.flowOn(ioDispatcher)
|
||||
.collectLatest { status ->
|
||||
val isAvailable = status !is NetworkStatus.Disconnected
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelProvider
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.UserspaceTunnel
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppSettingRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
@@ -20,6 +21,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.amnezia.awg.backend.Backend
|
||||
import org.amnezia.awg.backend.GoBackend
|
||||
import org.amnezia.awg.backend.RootTunnelActionHandler
|
||||
@@ -101,8 +103,8 @@ class TunnelModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideNetworkMonitor(@ApplicationContext context: Context): NetworkMonitor {
|
||||
return AndroidNetworkMonitor(context)
|
||||
fun provideNetworkMonitor(@ApplicationContext context: Context, settingsRepository: AppSettingRepository): NetworkMonitor {
|
||||
return AndroidNetworkMonitor(context) { runBlocking { settingsRepository.get().isWifiNameByShellEnabled } }
|
||||
}
|
||||
|
||||
@Singleton
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
object Constants {
|
||||
const val VERSION_NAME = "3.7.1"
|
||||
const val VERSION_NAME = "3.7.2"
|
||||
const val JVM_TARGET = "17"
|
||||
const val VERSION_CODE = 37100
|
||||
const val VERSION_CODE = 37200
|
||||
const val TARGET_SDK = 35
|
||||
const val MIN_SDK = 26
|
||||
const val APP_ID = "com.zaneschepke.wireguardautotunnel"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
What's new:
|
||||
- Auto tunnel regression fix
|
||||
- Tile sync improvements
|
||||
- Optimize wifi name querying
|
||||
- Improve network monitoring permission checks
|
||||
@@ -20,7 +20,7 @@ pinLockCompose = "1.0.4"
|
||||
roomVersion = "2.6.1"
|
||||
timber = "5.0.1"
|
||||
tunnel = "1.2.7"
|
||||
androidGradlePlugin = "8.10.0-beta01"
|
||||
androidGradlePlugin = "8.8.0-alpha05"
|
||||
kotlin = "2.1.10"
|
||||
ksp = "2.1.10-1.0.31"
|
||||
composeBom = "2025.03.00"
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
|
||||
>
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"
|
||||
tools:targetApi="29" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
</manifest>
|
||||
|
||||
|
||||
+109
-26
@@ -1,60 +1,134 @@
|
||||
package com.zaneschepke.networkmonitor
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.location.LocationManager
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.NetworkRequest
|
||||
import android.net.wifi.WifiManager
|
||||
import android.os.Build
|
||||
import com.wireguard.android.util.RootShell
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import timber.log.Timber
|
||||
|
||||
class AndroidNetworkMonitor(
|
||||
context: Context,
|
||||
private val useRootShellCallback: suspend () -> Boolean,
|
||||
) : NetworkMonitor {
|
||||
|
||||
companion object {
|
||||
const val LOCATION_GRANTED = "LOCATION_PERMISSIONS_GRANTED"
|
||||
const val LOCATION_SERVICES_FILTER = "android.location.PROVIDERS_CHANGED"
|
||||
}
|
||||
|
||||
private val appContext = context.applicationContext
|
||||
private val packageName = appContext.packageName
|
||||
private val connectivityManager = appContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
private val wifiManager = appContext.getSystemService(Context.WIFI_SERVICE) as WifiManager?
|
||||
private val locationManager = appContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
private val rootShell = RootShell(context)
|
||||
|
||||
private var includeWifiSsid = false
|
||||
private var useRootShell = false
|
||||
@get:Synchronized @set:Synchronized
|
||||
var currentSsid: String? = null
|
||||
|
||||
@get:Synchronized @set:Synchronized
|
||||
var wifiConnected = false
|
||||
|
||||
data class WifiState(val connected: Boolean = false, val ssid: String? = null)
|
||||
data class TransportState(val connected: Boolean = false)
|
||||
|
||||
private val wifiFlow: Flow<WifiState> = callbackFlow {
|
||||
|
||||
var currentSsid: String?
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
fun getWifiSsid(): String? {
|
||||
if (!includeWifiSsid || wifiManager == null) return null
|
||||
return if (useRootShell) {
|
||||
return if (runBlocking { useRootShellCallback() }) {
|
||||
rootShell.getCurrentWifiName()
|
||||
} else {
|
||||
wifiManager.connectionInfo?.ssid?.trim('"')?.takeIf {
|
||||
it != "<unknown>" && it.isNotEmpty()
|
||||
if (wifiManager == null) return null
|
||||
try {
|
||||
wifiManager.connectionInfo?.ssid?.trim('"')?.takeIf { it.isNotEmpty() }
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun handleUnknownWifi() {
|
||||
val newSsid = getWifiSsid()
|
||||
// Only update if new SSID is valid; preserve existing valid SSID otherwise
|
||||
if (newSsid != null && newSsid != WifiManager.UNKNOWN_SSID) {
|
||||
currentSsid = newSsid
|
||||
trySend(WifiState(connected = wifiConnected, ssid = currentSsid))
|
||||
} else if (currentSsid == null || currentSsid == WifiManager.UNKNOWN_SSID) {
|
||||
currentSsid = newSsid
|
||||
trySend(WifiState(connected = wifiConnected, ssid = currentSsid))
|
||||
}
|
||||
Timber.d("handleUnknownWifi: currentSsid=$currentSsid")
|
||||
}
|
||||
|
||||
val locationPermissionReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
Timber.d("locationPermissionReceiver received intent with action: ${intent.action}")
|
||||
if (intent.action == "$packageName.$LOCATION_GRANTED") {
|
||||
Timber.d("Received update: Precise and all-the-time location permissions are enabled")
|
||||
handleUnknownWifi()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val locationServicesReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action == LOCATION_SERVICES_FILTER) {
|
||||
val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
|
||||
val isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
|
||||
val isLocationServicesEnabled = isGpsEnabled || isNetworkEnabled
|
||||
Timber.d("Location Services state changed. Enabled: $isLocationServicesEnabled, GPS: $isGpsEnabled, Network: $isNetworkEnabled")
|
||||
if (isLocationServicesEnabled) handleUnknownWifi()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use RECEIVER_NOT_EXPORTED for Android 14+ compatibility
|
||||
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
Context.RECEIVER_EXPORTED
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
||||
appContext.registerReceiver(
|
||||
locationPermissionReceiver,
|
||||
IntentFilter("$packageName.$LOCATION_GRANTED"),
|
||||
flags,
|
||||
)
|
||||
|
||||
appContext.registerReceiver(
|
||||
locationServicesReceiver,
|
||||
IntentFilter(LOCATION_SERVICES_FILTER),
|
||||
flags,
|
||||
)
|
||||
|
||||
val callback = object : ConnectivityManager.NetworkCallback() {
|
||||
override fun onAvailable(network: Network) {
|
||||
Timber.d("Wi-Fi onAvailable: network=$network")
|
||||
currentSsid = getWifiSsid()
|
||||
wifiConnected = true
|
||||
trySend(WifiState(connected = true, ssid = currentSsid))
|
||||
}
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
Timber.d("Wi-Fi onLost: network=$network")
|
||||
currentSsid = null
|
||||
wifiConnected = false
|
||||
trySend(WifiState(connected = false, ssid = null))
|
||||
}
|
||||
|
||||
@@ -71,7 +145,11 @@ class AndroidNetworkMonitor(
|
||||
connectivityManager.registerNetworkCallback(request, callback)
|
||||
trySend(WifiState())
|
||||
|
||||
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
|
||||
awaitClose {
|
||||
connectivityManager.unregisterNetworkCallback(callback)
|
||||
appContext.unregisterReceiver(locationPermissionReceiver)
|
||||
appContext.unregisterReceiver(locationServicesReceiver)
|
||||
}
|
||||
}
|
||||
|
||||
private val cellularFlow: Flow<TransportState> = callbackFlow {
|
||||
@@ -95,7 +173,9 @@ class AndroidNetworkMonitor(
|
||||
connectivityManager.registerNetworkCallback(request, callback)
|
||||
trySend(TransportState())
|
||||
|
||||
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
|
||||
awaitClose {
|
||||
connectivityManager.unregisterNetworkCallback(callback)
|
||||
}
|
||||
}
|
||||
|
||||
private val ethernetFlow: Flow<TransportState> = callbackFlow {
|
||||
@@ -122,21 +202,24 @@ class AndroidNetworkMonitor(
|
||||
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
|
||||
}
|
||||
|
||||
override fun getNetworkStatusFlow(includeWifiSsid: Boolean, useRootShell: Boolean): Flow<NetworkStatus> {
|
||||
this.includeWifiSsid = includeWifiSsid
|
||||
this.useRootShell = useRootShell
|
||||
return combine(wifiFlow, cellularFlow, ethernetFlow) { wifi, cellular, ethernet ->
|
||||
val hasAnyConnection = wifi.connected || cellular.connected || ethernet.connected
|
||||
if (hasAnyConnection) {
|
||||
NetworkStatus.Connected(
|
||||
wifiSsid = wifi.ssid,
|
||||
wifiConnected = wifi.connected,
|
||||
cellularConnected = cellular.connected,
|
||||
ethernetConnected = ethernet.connected,
|
||||
)
|
||||
} else {
|
||||
NetworkStatus.Disconnected
|
||||
}.also { Timber.d("NetworkStatus: $it") }
|
||||
}.distinctUntilChanged()
|
||||
override val networkStatusFlow = combine(wifiFlow, cellularFlow, ethernetFlow) { wifi, cellular, ethernet ->
|
||||
val hasAnyConnection = wifi.connected || cellular.connected || ethernet.connected
|
||||
if (hasAnyConnection) {
|
||||
NetworkStatus.Connected(
|
||||
wifiSsid = wifi.ssid,
|
||||
wifiConnected = wifi.connected,
|
||||
cellularConnected = cellular.connected,
|
||||
ethernetConnected = ethernet.connected,
|
||||
)
|
||||
} else {
|
||||
NetworkStatus.Disconnected
|
||||
}.also { Timber.d("NetworkStatus: $it") }
|
||||
}.distinctUntilChanged()
|
||||
|
||||
override fun sendLocationPermissionsGrantedBroadcast() {
|
||||
val action = "$packageName.$LOCATION_GRANTED"
|
||||
val intent = Intent(action)
|
||||
Timber.d("Sending broadcast: $action")
|
||||
appContext.sendBroadcast(intent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,5 +3,6 @@ package com.zaneschepke.networkmonitor
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface NetworkMonitor {
|
||||
fun getNetworkStatusFlow(includeWifiSsid: Boolean, useRootShell: Boolean): Flow<NetworkStatus>
|
||||
val networkStatusFlow: Flow<NetworkStatus>
|
||||
fun sendLocationPermissionsGrantedBroadcast()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user