Compare commits

..

10 Commits

Author SHA1 Message Date
Zane Schepke cc408ed57e fix: nightly tagging 2025-04-15 15:43:33 -04:00
Zane Schepke 9b7841f487 fix: nightly tagging 2025-04-15 15:13:49 -04:00
Zane Schepke a1c663233d fix: space 2025-04-15 06:00:06 -04:00
Zane Schepke c520fa5ed2 fix: ci 2025-04-15 05:59:41 -04:00
Zane Schepke 120bde2939 ci: remove old r8 rules 2025-04-15 05:20:52 -04:00
Zane Schepke 58fcc358ce build: gradle checksum update 2025-04-15 04:57:43 -04:00
Zane Schepke 72722a0be5 fix: android dns issue
closes #687
2025-04-15 03:53:17 -04:00
Zane Schepke 29aba65690 ci: change to org ci vars 2025-04-14 15:52:14 -04:00
Tobias Wienkoop 5d9a534e1c feat: add missing monochrome app icon (#689) 2025-04-12 11:56:56 -04:00
Zane Schepke f5dafa6bf7 ci: fix fastlane 2025-04-11 22:28:59 -04:00
14 changed files with 96 additions and 164 deletions
+4 -4
View File
@@ -48,9 +48,9 @@ jobs:
build:
runs-on: ubuntu-latest
env:
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
SIGNING_KEY_ALIAS: ${{ secrets.ANDROID_SIGNING_KEY_ALIAS }}
SIGNING_KEY_PASSWORD: ${{ secrets.ANDROID_SIGNING_KEY_PASSWORD }}
SIGNING_STORE_PASSWORD: ${{ secrets.ANDROID_SIGNING_STORE_PASSWORD }}
KEY_STORE_FILE: 'android_keystore.jks'
KEY_STORE_LOCATION: ${{ github.workspace }}/app/keystore/
outputs:
@@ -74,7 +74,7 @@ jobs:
with:
fileName: ${{ env.KEY_STORE_FILE }}
fileDir: ${{ env.KEY_STORE_LOCATION }}
encodedString: ${{ secrets.KEYSTORE }}
encodedString: ${{ secrets.ANDROID_KEYSTORE }}
# create keystore path for gradle to read
- name: Create keystore path env var
+25 -11
View File
@@ -34,6 +34,9 @@ on:
env:
UPLOAD_DIR_ANDROID: android_artifacts
permissions:
contents: write
jobs:
check_commits:
name: Check for New Commits
@@ -50,7 +53,7 @@ jobs:
- name: Check for new commits
id: check
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.PAT }}
run: |
# This script checks for commits newer than 23 hours ago
NEW_COMMITS=$(git rev-list --count --after="$(date -Iseconds -d '23 hours ago')" ${{ github.sha }})
@@ -71,9 +74,9 @@ jobs:
name: publish-github
runs-on: ubuntu-latest
env:
GH_USER: ${{ secrets.GH_USER }}
GH_USER: ${{ secrets.PAT_USERNAME }}
# GH needed for gh cli
GH_TOKEN: ${{ secrets.GH_TOKEN }}
GH_TOKEN: ${{ secrets.PAT }}
GH_REPO: ${{ github.repository }}
steps:
@@ -90,8 +93,19 @@ jobs:
tag: "latest" # or any tag name you wish to use
message: "Automated tag for HEAD commit"
force_push_tag: true
github_token: ${{ secrets.GITHUB_TOKEN }}
tag_exists_error: false
- name: Create Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: latest
release_name: Automated Latest tag
draft: false
prerelease: false
- name: Get latest release
id: latest_release
uses: kaliber5/action-get-release@v1
@@ -118,7 +132,7 @@ jobs:
if: ${{ inputs.release_type == '' || inputs.release_type == 'nightly' || inputs.release_type == 'prerelease' }}
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
github_token: ${{ secrets.PAT }}
branch: ${{ github.ref }}
- name: Make download dir
@@ -168,7 +182,7 @@ jobs:
id: create_release
uses: softprops/action-gh-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
GITHUB_TOKEN: ${{ secrets.PAT }}
with:
body: |
${{ env.RELEASE_NOTES }}
@@ -220,13 +234,13 @@ jobs:
runs-on: ubuntu-latest
env:
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
SIGNING_KEY_ALIAS: ${{ secrets.ANDROID_SIGNING_KEY_ALIAS }}
SIGNING_KEY_PASSWORD: ${{ secrets.ANDROID_SIGNING_KEY_PASSWORD }}
SIGNING_STORE_PASSWORD: ${{ secrets.ANDROID_SIGNING_STORE_PASSWORD }}
KEY_STORE_FILE: 'android_keystore.jks'
KEY_STORE_LOCATION: ${{ github.workspace }}/app/keystore/
GH_USER: ${{ secrets.GH_USER }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
GH_USER: ${{ secrets.PAT_USERNAME }}
GH_TOKEN: ${{ secrets.PAT }}
steps:
- uses: actions/checkout@v4
@@ -248,7 +262,7 @@ jobs:
with:
fileName: ${{ env.KEY_STORE_FILE }}
fileDir: ${{ env.KEY_STORE_LOCATION }}
encodedString: ${{ secrets.KEYSTORE }}
encodedString: ${{ secrets.ANDROID_KEYSTORE }}
# create keystore path for gradle to read
- name: Create keystore path env var
-42
View File
@@ -1,42 +0,0 @@
-dontwarn com.google.errorprone.annotations.**
-keepclassmembers class * extends androidx.datastore.preferences.protobuf.GeneratedMessageLite {
<fields>;
}
# Keep all classes in the org.xbill.DNS package and subpackages
-keep class org.xbill.DNS.** { *; }
-dontwarn org.xbill.DNS.**
# Preserve JNA classes if used (e.g., for IPHlpAPI on Windows)
-keep class com.sun.jna.** { *; }
-dontwarn com.sun.jna.**
# Keep DNS resolver configuration classes that might be loaded dynamically
-keep class org.xbill.DNS.config.** { *; }
-dontwarn org.xbill.DNS.config.**
-keep class org.xbill.DNS.** { *; }
# Prevent optimization issues with native or reflection-based calls
-dontoptimize
-dontshrink
# Uncomment the above if errors persist, but use sparingly as theyre broad
# Suppress warnings about missing classes if not all features are used
-dontwarn java.lang.management.**
-dontwarn sun.nio.ch.**
-dontwarn com.google.api.client.http.GenericUrl
-dontwarn com.google.api.client.http.HttpHeaders
-dontwarn com.google.api.client.http.HttpRequest
-dontwarn com.google.api.client.http.HttpRequestFactory
-dontwarn com.google.api.client.http.HttpResponse
-dontwarn com.google.api.client.http.HttpTransport
-dontwarn com.google.api.client.http.javanet.NetHttpTransport$Builder
-dontwarn com.google.api.client.http.javanet.NetHttpTransport
-dontwarn javax.lang.model.element.Modifier
-dontwarn org.joda.time.Instant
-dontwarn org.slf4j.impl.StaticLoggerBinder
-dontwarn org.slf4j.impl.StaticMDCBinder
-dontwarn org.slf4j.impl.StaticMarkerBinder
-61
View File
@@ -1,61 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-keepclassmembers class * extends androidx.datastore.preferences.protobuf.GeneratedMessageLite {
<fields>;
}
# Keep all classes in the org.xbill.DNS package and subpackages
-keep class org.xbill.DNS.** { *; }
-dontwarn org.xbill.DNS.**
# Preserve JNA classes if used (e.g., for IPHlpAPI on Windows)
-keep class com.sun.jna.** { *; }
-dontwarn com.sun.jna.**
# Keep DNS resolver configuration classes that might be loaded dynamically
-keep class org.xbill.DNS.config.** { *; }
-dontwarn org.xbill.DNS.config.**
-keep class org.xbill.DNS.** { *; }
# Prevent optimization issues with native or reflection-based calls
-dontoptimize
-dontshrink
# Uncomment the above if errors persist, but use sparingly as theyre broad
# Suppress warnings about missing classes if not all features are used
-dontwarn java.lang.management.**
-dontwarn sun.nio.ch.**
-dontwarn com.google.api.client.http.GenericUrl
-dontwarn com.google.api.client.http.HttpHeaders
-dontwarn com.google.api.client.http.HttpRequest
-dontwarn com.google.api.client.http.HttpRequestFactory
-dontwarn com.google.api.client.http.HttpResponse
-dontwarn com.google.api.client.http.HttpTransport
-dontwarn com.google.api.client.http.javanet.NetHttpTransport$Builder
-dontwarn com.google.api.client.http.javanet.NetHttpTransport
-dontwarn javax.lang.model.element.Modifier
-dontwarn org.joda.time.Instant
-dontwarn org.slf4j.impl.StaticLoggerBinder
-dontwarn org.slf4j.impl.StaticMDCBinder
-dontwarn org.slf4j.impl.StaticMarkerBinder
+1
View File
@@ -66,6 +66,7 @@
<activity
android:name=".MainActivity"
android:exported="true"
android:banner="@mipmap/ic_banner"
android:windowSoftInputMode="adjustNothing"
android:theme="@style/Theme.WireguardAutoTunnel"
android:configChanges="orientation|screenSize|keyboardHidden"
@@ -3,17 +3,13 @@ package com.zaneschepke.wireguardautotunnel.domain.entity
import com.wireguard.android.backend.Tunnel
import com.wireguard.config.Config
import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.extensions.defaultName
import com.zaneschepke.wireguardautotunnel.util.extensions.extractNameAndNumber
import com.zaneschepke.wireguardautotunnel.util.extensions.hasNumberInParentheses
import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable
import com.zaneschepke.wireguardautotunnel.util.extensions.toWgQuickString
import com.zaneschepke.wireguardautotunnel.util.extensions.*
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.io.InputStream
import java.net.InetAddress
import java.nio.charset.StandardCharsets
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.withContext
import timber.log.Timber
data class TunnelConf(
val id: Int = 0,
@@ -30,6 +26,7 @@ data class TunnelConf(
val pingIp: String? = null,
val isEthernetTunnel: Boolean = false,
val isIpv4Preferred: Boolean = true,
val useCache: Boolean = false,
@Transient private var stateChangeCallback: ((Any) -> Unit)? = null,
) : Tunnel, org.amnezia.awg.backend.Tunnel {
@@ -97,19 +94,9 @@ data class TunnelConf(
)
.apply {
stateChangeCallback = this@TunnelConf.stateChangeCallback
// tunnelStatsCallback = this@TunnelConf.tunnelStatsCallback
// bounceTunnelCallback = this@TunnelConf.bounceTunnelCallback
}
}
// fun onUpdateStatistics() {
// tunnelStatsCallback?.invoke()
// }
//
// fun bounceTunnel(tunnelConf: TunnelConf, reason: TunnelStatus.StopReason) {
// bounceTunnelCallback?.invoke(tunnelConf, reason)
// }
fun toAmConfig(): org.amnezia.awg.config.Config {
return configFromAmQuick(amQuick.ifBlank { wgQuick })
}
@@ -122,6 +109,8 @@ data class TunnelConf(
override fun isIpv4ResolutionPreferred(): Boolean = isIpv4Preferred
override fun useCache(): Boolean = useCache
override fun onStateChange(newState: org.amnezia.awg.backend.Tunnel.State) {
stateChangeCallback?.invoke(newState)
}
@@ -130,12 +119,6 @@ data class TunnelConf(
stateChangeCallback?.invoke(newState)
}
fun isTunnelConfigChanged(updatedConf: TunnelConf): Boolean {
return updatedConf.wgQuick != wgQuick ||
updatedConf.amQuick != amQuick ||
updatedConf.name != name
}
fun generateUniqueName(tunnelNames: List<String>): String {
var tunnelName = this.tunName
var num = 1
@@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
@@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
+6
View File
@@ -3,11 +3,17 @@
<style name="Theme.WireguardAutoTunnel" parent="@style/Theme.AppCompat.NoActionBar">
<item name="android:windowBackground">@color/background</item>
<item name="android:colorPrimary">@color/background</item>
<item name="android:windowAllowReturnTransitionOverlap">true</item>
<item name="android:windowAllowEnterTransitionOverlap">true</item>
</style>
<style name="Theme.App.Start" parent="@style/Theme.SplashScreen">
<item name="windowSplashScreenBackground">@color/background</item>
<item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher</item>
<item name="postSplashScreenTheme">@style/Theme.WireguardAutoTunnel</item>
<item name="android:colorPrimary">@color/background</item>
<item name="android:windowAllowReturnTransitionOverlap">true</item>
<item name="android:windowAllowEnterTransitionOverlap">true</item>
</style>
</resources>
@@ -0,0 +1,14 @@
Features
- Add tunnels via .conf file, zip, manual entry, or QR code
- Auto connect to VPN based on Wi-Fi SSID, ethernet, or mobile data
- Split tunneling by application with search
- WireGuard support for kernel and userspace modes
- Amnezia support for userspace mode for DPI/censorship protection
- Always-On VPN support
- Export Amnezia and WireGuard tunnels to zip
- Quick tile support for VPN toggling
- Static shortcuts support for primary tunnel for automation integration
- Intent automation support for all tunnels
- Automatic service restart after reboot
- Battery preservation measures
+3 -3
View File
@@ -1,7 +1,7 @@
[versions]
accompanist = "0.37.2"
activityCompose = "1.10.1"
amneziawgAndroid = "1.3.4"
amneziawgAndroid = "1.3.8"
androidx-junit = "1.2.1"
appcompat = "1.7.0"
biometricKtx = "1.2.0-alpha05"
@@ -19,8 +19,8 @@ navigationCompose = "2.8.9"
pinLockCompose = "1.0.4"
roomVersion = "2.7.0"
timber = "5.0.1"
tunnel = "1.2.11"
androidGradlePlugin = "8.8.0-alpha05"
tunnel = "1.2.14"
androidGradlePlugin = "8.9.1"
kotlin = "2.1.20"
ksp = "2.1.20-2.0.0"
composeBom = "2025.04.00"
+2 -2
View File
@@ -1,8 +1,8 @@
#Wed Oct 11 22:39:21 EDT 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
distributionSha256Sum=31c55713e40233a8303827ceb42ca48a47267a0ad4bab9177123121e71524c26
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
distributionSha256Sum=f397b287023acdba1e9f6fc5ea72d22dd63669d59ed4a289a29b1a76eee151c6
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
@@ -12,12 +12,15 @@ import android.net.NetworkRequest
import android.net.wifi.WifiManager
import android.os.Build
import com.wireguard.android.util.RootShell
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
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 kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
class AndroidNetworkMonitor(
@@ -39,6 +42,8 @@ class AndroidNetworkMonitor(
appContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
private val rootShell = RootShell(context)
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
@get:Synchronized @set:Synchronized var currentSsid: String? = null
@get:Synchronized @set:Synchronized var wifiConnected = false
@@ -48,22 +53,26 @@ class AndroidNetworkMonitor(
data class TransportState(val connected: Boolean = false)
private val wifiFlow: Flow<WifiState> = callbackFlow {
@Suppress("DEPRECATION")
fun getWifiSsid(): String? {
return if (runBlocking { useRootShellCallback() }) {
rootShell.getCurrentWifiName()
} else {
if (wifiManager == null) return null
try {
wifiManager.connectionInfo?.ssid?.trim('"')?.takeIf { it.isNotEmpty() }
} catch (e: Exception) {
Timber.e(e)
null
suspend fun getWifiSsid(): String? {
return withContext(ioDispatcher) {
if (useRootShellCallback()) {
rootShell.getCurrentWifiName()
} else {
if (wifiManager == null) return@withContext null
try {
wifiManager.connectionInfo?.ssid?.trim('"')?.takeIf { it.isNotEmpty() }
} catch (e: Exception) {
Timber.e(e)
null
}
}
}
}
fun handleUnknownWifi() {
suspend fun handleUnknownWifi() {
val newSsid = getWifiSsid()
// Only update if new SSID is valid; preserve existing valid SSID otherwise
if (newSsid != null && newSsid != WifiManager.UNKNOWN_SSID) {
@@ -86,7 +95,9 @@ class AndroidNetworkMonitor(
Timber.d(
"Received update: Precise and all-the-time location permissions are enabled"
)
handleUnknownWifi()
launch {
handleUnknownWifi()
}
}
}
}
@@ -103,7 +114,9 @@ class AndroidNetworkMonitor(
Timber.d(
"Location Services state changed. Enabled: $isLocationServicesEnabled, GPS: $isGpsEnabled, Network: $isNetworkEnabled"
)
if (isLocationServicesEnabled) handleUnknownWifi()
if (isLocationServicesEnabled) launch {
handleUnknownWifi()
}
}
}
}
@@ -132,9 +145,11 @@ class AndroidNetworkMonitor(
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))
launch {
currentSsid = getWifiSsid()
wifiConnected = true
trySend(WifiState(connected = true, ssid = currentSsid))
}
}
override fun onLost(network: Network) {
+1 -1
View File
@@ -1 +1 @@
2
0