Compare commits

..

44 Commits

Author SHA1 Message Date
Jason A. Donenfeld 79c5eb41c3 version: bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-05-04 16:48:23 +02:00
Jason A. Donenfeld 9e30c7d2e9 strings: sync with crowdin
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-05-03 15:41:41 +02:00
Jason A. Donenfeld d80129ba46 gradle: use += when possible
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-05-03 15:37:06 +02:00
Jason A. Donenfeld 6907663f07 gradle: don't encourage sticking credentials in files
We've only ever used this via environment variables, so keep it that
way.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-05-03 14:19:19 +02:00
Jason A. Donenfeld dca96a52b4 gradle: reabstract package name
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-05-03 14:09:11 +02:00
Jason A. Donenfeld cf4fca8086 gradle: remove runtime detection of play store
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-05-04 16:45:01 +02:00
Jason A. Donenfeld 318f36a205 README: account for java version updates
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-05-03 14:23:54 +02:00
Jason A. Donenfeld 87aa73cc64 gradle: use newer desugaring
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-05-03 14:23:38 +02:00
Jason A. Donenfeld 4e28d57aee gradle: use Java 17
It appears to work, even on API 21.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-05-03 14:01:05 +02:00
Harsh Shandilya 6d67a3d5f5 gradle: upgrade Kotlin and AGP
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2023-05-03 00:52:52 +05:30
Harsh Shandilya 1b5f4d6dfd gradle: move JVM target up to 11
1.8 is being deprecated across the board

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2023-05-03 00:49:47 +05:30
Harsh Shandilya bed2f2e5d6 gradle: convert build files to Kotlin
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2023-05-03 00:43:18 +05:30
Harsh Shandilya 7d91666860 gradle: update wrapper to 8.1.1
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2023-04-24 11:22:21 +05:30
Jason A. Donenfeld 8800f861ed ui: update cleanups
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-05-03 14:28:40 +02:00
Jason A. Donenfeld 99336f6552 version: bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-05-02 17:59:44 +02:00
Jason A. Donenfeld 0b7b65296b gradle: downgrade wrapper to 8.0.1
This reverts commit 455fcc076becafdf21944aaf2c1f1e9086eb2fd9 because of
f-droid.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-05-05 12:56:10 +02:00
Jason A. Donenfeld e2f931b4cb ui: use .seconds instead of .toDuration
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-05-05 12:55:51 +02:00
Jason A. Donenfeld d6ad7d11d0 ui: handle update signatures
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-05-01 16:24:41 +02:00
Jason A. Donenfeld 914917036e version: bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-27 15:52:23 +02:00
Jason A. Donenfeld 6e9ca435ca gradle: trim experimental list
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-27 13:51:25 +02:00
Jason A. Donenfeld 0797e99559 proguard: cleanup unused parts
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-27 13:12:02 +02:00
Jason A. Donenfeld 0ba064ff48 build: bump activity-ktx and fragment-ktx
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-25 14:53:41 +02:00
Jason A. Donenfeld b4fe9efadb strings: sync with crowdin
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-13 18:49:36 +02:00
Jason A. Donenfeld b1b08ce716 ui: display latest handshake time
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-24 18:07:03 +02:00
Jason A. Donenfeld 20480992c4 tunnel: export latest handshake stat
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-24 18:06:57 +02:00
Harsh Shandilya 9df90b259a gradle: update wrapper to 8.1.1
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2023-04-24 11:22:21 +05:30
Jason A. Donenfeld b94a412c09 tools: build elf-cleaner using system compiler
Rather than using the NDK compiler for the host arch and hoping it picks
up on system library locations, just use `cc` and rely on the
traditional symlink to resolve that to the default host compiler.

This should address build errors seen on macOS.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-21 15:43:25 +02:00
Jason A. Donenfeld 0b0a553123 ui: remove unused extra_margin constant
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-13 19:36:20 +02:00
Jason A. Donenfeld 6d4d2f4ebf tools: ndk-compat: NDK now is minimum API 19
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-13 19:24:50 +02:00
Jason A. Donenfeld e55358f685 build: bump AGP to 8.0.0
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-27 14:21:47 +02:00
Jason A. Donenfeld c2fdd0ca93 ui: layout: don't stretch text fields, so that alignment is correct
This makes things look uniform on RTL.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-13 17:47:36 +02:00
Jason A. Donenfeld 19044b0f53 ui: LogViewerActivity: cleanup ret val
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-13 16:06:54 +02:00
Jason A. Donenfeld d4e77e6d57 version: bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-12 15:48:04 +02:00
Jason A. Donenfeld 6ba3e4f001 tools: do not put vcs info into go binary
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-12 15:48:01 +02:00
Jason A. Donenfeld 8152191277 ui: further reduce log viewer memory usage
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-12 13:48:55 +02:00
Jason A. Donenfeld 8c6c909e98 tools: bump go
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-11 19:52:30 +02:00
Jason A. Donenfeld fdda87fd31 tunnel: defer enabling strict mode until later
Startup code can be synchronous.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-11 19:20:26 +02:00
Jason A. Donenfeld 3907f6a4c1 tunnel: do not keep service running when bringing tunnel down
Doing so causes the process to restart when the activity is cleared,
even when no tunnel is running. I fear this might also lead to the
current flurry of DeadSystemExceptions.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-11 19:17:55 +02:00
Jason A. Donenfeld 652971d746 tools: clean compile options a bit
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-11 16:23:37 +02:00
Jason A. Donenfeld 1bf8d7e4c4 build: shrink resources
Saves 300k.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-10 18:19:52 +02:00
Jason A. Donenfeld 3c834ac8e3 ui: always show visible localized error messages
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-07 15:22:00 +02:00
Jason A. Donenfeld 8669ae78e1 ui: set app list button text on main thread
Hopefully will fix, "Only the original thread that creted a view
hierarchy can touch its views."

Reported-by: Damir <2k1dmg@gmail.com>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-07 15:18:33 +02:00
Jason A. Donenfeld 53f8c8dbc4 build: remove bespoke signing config properties
Keys should be in HSMs anyway, and a setup like this does not easily
allow for that.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-06 17:16:29 +02:00
Jason A. Donenfeld 1328660b0b build: bump deps
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-04-05 22:19:37 +02:00
54 changed files with 3746 additions and 530 deletions
+1 -1
View File
@@ -14,5 +14,5 @@ build/
*.dex
*.iml
*.jks
keystore.properties
gradlew.bat
maint/
+3 -3
View File
@@ -26,12 +26,12 @@ The library makes use of Java 8 features, so be sure to support those in your gr
```
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
coreLibraryDesugaringEnabled = true
}
dependencies {
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5"
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.0.3"
}
```
-33
View File
@@ -1,33 +0,0 @@
buildscript {
ext {
activityVersion = '1.6.1'
annotationsVersion = '1.6.0'
appcompatVersion = '1.6.1'
biometricVersion = '1.1.0'
collectionVersion = '1.2.0'
constraintLayoutVersion = '2.1.4'
coordinatorLayoutVersion = '1.2.0'
coreKtxVersion = '1.10.0-rc01'
coroutinesVersion = '1.6.4'
datastoreVersion = '1.0.0'
desugarVersion = '1.1.5'
fragmentVersion = '1.5.5'
jsr305Version = '3.0.2'
junitVersion = '4.13.2'
lifecycleRuntimeKtxVersion = '2.6.0'
materialComponentsVersion = '1.8.0'
preferenceVersion = '1.2.0'
zxingEmbeddedVersion = '4.3.0'
groupName = 'com.wireguard.android'
}
}
tasks {
wrapper {
gradleVersion = "8.0.2"
distributionSha256Sum = "ff7bf6a86f09b9b2c40bb8f48b25fc19cf2b2664fd1d220cd7ab833ec758d0d7"
}
}
apply from: "version.gradle"
+13
View File
@@ -0,0 +1,13 @@
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.android.library) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.kapt) apply false
}
tasks {
wrapper {
gradleVersion = "8.1.1"
distributionSha256Sum = "e111cb9948407e26351227dabce49822fb88c37ee72f1d1582a69c68af2e702f"
}
}
+6 -4
View File
@@ -1,3 +1,7 @@
wireguardVersionCode=502
wireguardVersionName=1.0.20230504
wireguardPackageName=com.wireguard.android
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
@@ -42,8 +46,6 @@ android.generateManifestClass=true
android.experimental.enableSourceSetPathsMap=true
# Use relative paths for better Gradle caching of library build tasks
android.experimental.cacheCompileLibResources=true
# Disable automatic creation of publishing components
android.disableAutomaticComponentCreation=true
# Default Android build features
# Disable BuildConfig generation by default
@@ -61,8 +63,8 @@ android.library.defaults.buildfeatures.androidresources=false
# Suppress warnings for some features that aren't yet stabilized
android.suppressUnsupportedOptionWarnings=android.keepWorkerActionServicesBetweenBuilds,\
android.experimental.enableNewResourceShrinker,android.experimental.enableNewResourceShrinker.preciseShrinking,\
android.enableAppCompileTimeRClass,android.experimental.enableSourceSetPathsMap,android.experimental.cacheCompileLibResources,\
android.experimental.enableNewResourceShrinker.preciseShrinking,\
android.enableAppCompileTimeRClass,\
android.suppressUnsupportedOptionWarnings
# OSSRH sometimes struggles with slow deployments, so this makes Gradle
+29
View File
@@ -0,0 +1,29 @@
[versions]
agp = "8.0.1"
kotlin = "1.8.21"
[libraries]
androidx-activity-ktx = "androidx.activity:activity-ktx:1.7.1"
androidx-annotation = "androidx.annotation:annotation:1.6.0"
androidx-appcompat = "androidx.appcompat:appcompat:1.6.1"
androidx-biometric = "androidx.biometric:biometric:1.1.0"
androidx-collection = "androidx.collection:collection:1.2.0"
androidx-constraintlayout = "androidx.constraintlayout:constraintlayout:2.1.4"
androidx-coordinatorlayout = "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
androidx-core-ktx = "androidx.core:core-ktx:1.10.0"
androidx-datastore-preferences = "androidx.datastore:datastore-preferences:1.0.0"
androidx-fragment-ktx = "androidx.fragment:fragment-ktx:1.5.7"
androidx-lifecycle-runtime-ktx = "androidx.lifecycle:lifecycle-runtime-ktx:2.6.1"
androidx-preference-ktx = "androidx.preference:preference-ktx:1.2.0"
desugarJdkLibs = "com.android.tools:desugar_jdk_libs:2.0.3"
google-material = "com.google.android.material:material:1.8.0"
jsr305 = "com.google.code.findbugs:jsr305:3.0.2"
junit = "junit:junit:4.13.2"
kotlinx-coroutines-android = "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
zxing-android-embedded = "com.journeyapps:zxing-android-embedded:4.3.0"
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
Binary file not shown.
+2 -2
View File
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=ff7bf6a86f09b9b2c40bb8f48b25fc19cf2b2664fd1d220cd7ab833ec758d0d7
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
distributionSha256Sum=e111cb9948407e26351227dabce49822fb88c37ee72f1d1582a69c68af2e702f
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+4 -3
View File
@@ -85,9 +85,6 @@ done
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -197,6 +194,10 @@ if "$cygwin" || "$msys" ; then
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
-28
View File
@@ -1,28 +0,0 @@
pluginManagement {
def agpVersion = '7.4.2'
def kotlinVersion = "1.8.0"
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
plugins {
id("com.android.application") version "$agpVersion"
id("com.android.library") version "$agpVersion"
id("org.jetbrains.kotlin.android") version "$kotlinVersion"
id("org.jetbrains.kotlin.kapt") version "$kotlinVersion"
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "wireguard-android"
include ':tunnel'
include ':ui'
+22
View File
@@ -0,0 +1,22 @@
@file:Suppress("UnstableApiUsage")
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "wireguard-android"
include(":tunnel")
include(":ui")
-72
View File
@@ -1,72 +0,0 @@
plugins {
id 'com.android.library'
}
version wireguardVersionName
group groupName
android {
compileSdk 33
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
namespace 'com.wireguard.android.tunnel'
defaultConfig {
minSdkVersion 21
targetSdkVersion 33
versionCode wireguardVersionCode
versionName wireguardVersionName
}
externalNativeBuild {
cmake {
path 'tools/CMakeLists.txt'
}
}
testOptions.unitTests.all {
testLogging {
events 'passed', 'skipped', 'failed', 'standardOut', 'standardError'
}
}
buildTypes {
all {
externalNativeBuild {
cmake {
targets 'libwg-go.so', 'libwg.so', 'libwg-quick.so'
arguments "-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}"
}
}
}
release {
externalNativeBuild {
cmake {
arguments "-DANDROID_PACKAGE_NAME=${groupName}"
}
}
}
debug {
externalNativeBuild {
cmake {
arguments "-DANDROID_PACKAGE_NAME=${groupName}.debug"
}
}
}
}
lint {
disable 'LongLogTag', 'NewApi'
}
publishing {
multipleVariants("release") {
allVariants()
}
}
}
dependencies {
implementation "androidx.annotation:annotation:$annotationsVersion"
implementation "androidx.collection:collection:$collectionVersion"
compileOnly "com.google.code.findbugs:jsr305:$jsr305Version"
testImplementation "junit:junit:$junitVersion"
}
apply from: "publish.gradle"
+130
View File
@@ -0,0 +1,130 @@
@file:Suppress("UnstableApiUsage")
import org.gradle.api.tasks.testing.logging.TestLogEvent
val pkg: String = providers.gradleProperty("wireguardPackageName").get()
plugins {
alias(libs.plugins.android.library)
`maven-publish`
signing
}
android {
compileSdk = 33
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
namespace = "${pkg}.tunnel"
defaultConfig {
minSdk = 21
}
externalNativeBuild {
cmake {
path("tools/CMakeLists.txt")
}
}
testOptions.unitTests.all {
it.testLogging { events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) }
}
buildTypes {
all {
externalNativeBuild {
cmake {
targets("libwg-go.so", "libwg.so", "libwg-quick.so")
arguments("-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}")
}
}
}
release {
externalNativeBuild {
cmake {
arguments("-DANDROID_PACKAGE_NAME=${pkg}")
}
}
}
debug {
externalNativeBuild {
cmake {
arguments("-DANDROID_PACKAGE_NAME=${pkg}.debug")
}
}
}
create("googleplay") {
initWith(getByName("release"))
}
}
lint {
disable += "LongLogTag"
disable += "NewApi"
}
publishing {
singleVariant("release") {
withJavadocJar()
withSourcesJar()
}
}
}
dependencies {
implementation(libs.androidx.annotation)
implementation(libs.androidx.collection)
compileOnly(libs.jsr305)
testImplementation(libs.junit)
}
publishing {
publications {
register<MavenPublication>("release") {
groupId = pkg
artifactId = "tunnel"
version = providers.gradleProperty("wireguardVersionName").get()
afterEvaluate {
from(components["release"])
}
pom {
name.set("WireGuard Tunnel Library")
description.set("Embeddable tunnel library for WireGuard for Android")
url.set("https://www.wireguard.com/")
licenses {
license {
name.set("The Apache Software License, Version 2.0")
url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
distribution.set("repo")
}
}
scm {
connection.set("scm:git:https://git.zx2c4.com/wireguard-android")
developerConnection.set("scm:git:https://git.zx2c4.com/wireguard-android")
url.set("https://git.zx2c4.com/wireguard-android")
}
developers {
organization {
name.set("WireGuard")
url.set("https://www.wireguard.com/")
}
developer {
name.set("WireGuard")
email.set("team@wireguard.com")
}
}
}
}
}
repositories {
maven {
name = "sonatype"
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
credentials {
username = providers.environmentVariable("SONATYPE_USER").orNull
password = providers.environmentVariable("SONATYPE_PASSWORD").orNull
}
}
}
}
signing {
useGpgCmd()
sign(publishing.publications)
}
-82
View File
@@ -1,82 +0,0 @@
apply plugin: 'maven-publish'
apply plugin: 'signing'
afterEvaluate {
publishing {
publications {
release(MavenPublication) {
groupId = groupName
artifactId = 'tunnel'
version wireguardVersionName
artifact sourcesJar
artifact javadocJar
from components.getByName("release")
pom {
name = 'WireGuard Tunnel Library'
description = 'Embeddable tunnel library for WireGuard for Android'
url = 'https://www.wireguard.com/'
licenses {
license {
name = 'The Apache Software License, Version 2.0'
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
distribution = 'repo'
}
}
scm {
connection = 'scm:git:https://git.zx2c4.com/wireguard-android'
developerConnection = 'scm:git:https://git.zx2c4.com/wireguard-android'
url = 'https://git.zx2c4.com/wireguard-android'
}
developers {
organization {
name = 'WireGuard'
url = 'https://www.wireguard.com/'
}
developer {
name = 'WireGuard'
email = 'team@wireguard.com'
}
}
}
}
}
repositories {
maven {
name = "sonatype"
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
credentials {
username = hasProperty('SONATYPE_USER') ? getProperty('SONATYPE_USER') : System.getenv('SONATYPE_USER')
password = hasProperty('SONATYPE_PASSWORD') ? getProperty('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
}
}
}
}
}
android.libraryVariants.all { variant ->
if (variant.name == 'release') {
task javadoc(type: Javadoc) {
source = variant.javaCompileProvider.get().source
classpath = files((android.bootClasspath.join(File.pathSeparator)))
classpath += variant.javaCompileProvider.get().classpath
title = 'Embeddable WireGuard Tunnel for Android v$wireguardVersionName'
}
task javadocJar(type: Jar, dependsOn: javadoc) {
archiveClassifier = 'javadoc'
from javadoc.destinationDir
}
task sourcesJar(type: Jar) {
archiveClassifier = 'sources'
from android.sourceSets.main.java.srcDirs
}
}
}
signing {
useGpgCmd()
sign publishing.publications
}
@@ -24,6 +24,7 @@ import com.wireguard.crypto.KeyFormatException;
import com.wireguard.util.NonNullForAll;
import java.net.InetAddress;
import java.time.Instant;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ExecutionException;
@@ -125,12 +126,14 @@ public final class GoBackend implements Backend {
Key key = null;
long rx = 0;
long tx = 0;
long latestHandshakeMSec = 0;
for (final String line : config.split("\\n")) {
if (line.startsWith("public_key=")) {
if (key != null)
stats.add(key, rx, tx);
stats.add(key, rx, tx, latestHandshakeMSec);
rx = 0;
tx = 0;
latestHandshakeMSec = 0;
try {
key = Key.fromHex(line.substring(11));
} catch (final KeyFormatException ignored) {
@@ -152,10 +155,26 @@ public final class GoBackend implements Backend {
} catch (final NumberFormatException ignored) {
tx = 0;
}
} else if (line.startsWith("last_handshake_time_sec=")) {
if (key == null)
continue;
try {
latestHandshakeMSec += Long.parseLong(line.substring(24)) * 1000;
} catch (final NumberFormatException ignored) {
latestHandshakeMSec = 0;
}
} else if (line.startsWith("last_handshake_time_nsec=")) {
if (key == null)
continue;
try {
latestHandshakeMSec += Long.parseLong(line.substring(25)) / 1000000;
} catch (final NumberFormatException ignored) {
latestHandshakeMSec = 0;
}
}
}
if (key != null)
stats.add(key, rx, tx);
stats.add(key, rx, tx, latestHandshakeMSec);
return stats;
}
@@ -324,6 +343,9 @@ public final class GoBackend implements Backend {
currentTunnelHandle = -1;
currentConfig = null;
wgTurnOff(handleToClose);
try {
vpnService.get(0, TimeUnit.NANOSECONDS).stopSelf();
} catch (final TimeoutException ignored) { }
}
tunnel.onStateChange(state);
@@ -6,36 +6,65 @@
package com.wireguard.android.backend;
import android.os.SystemClock;
import android.util.Pair;
import com.wireguard.crypto.Key;
import com.wireguard.util.NonNullForAll;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import androidx.annotation.Nullable;
/**
* Class representing transfer statistics for a {@link Tunnel} instance.
*/
@NonNullForAll
public class Statistics {
private final Map<Key, Pair<Long, Long>> peerBytes = new HashMap<>();
// TODO: switch to Java Record class once R8 supports desugaring those.
public final class PeerStats {
public final long rxBytes, txBytes, latestHandshakeEpochMillis;
PeerStats(final long rxBytes, final long txBytes, final long latestHandshakeEpochMillis) {
this.rxBytes = rxBytes;
this.txBytes = txBytes;
this.latestHandshakeEpochMillis = latestHandshakeEpochMillis;
}
@Override public boolean equals(final Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
final PeerStats stats = (PeerStats) o;
return rxBytes == stats.rxBytes && txBytes == stats.txBytes && latestHandshakeEpochMillis == stats.latestHandshakeEpochMillis;
}
@Override public int hashCode() {
return Objects.hash(rxBytes, txBytes, latestHandshakeEpochMillis);
}
}
private final Map<Key, PeerStats> stats = new HashMap<>();
private long lastTouched = SystemClock.elapsedRealtime();
Statistics() {
}
/**
* Add a peer and its current data usage to the internal map.
* Add a peer and its current stats to the internal map.
*
* @param key A WireGuard public key bound to a particular peer
* @param rx The received traffic for the {@link com.wireguard.config.Peer} referenced by
* the provided {@link Key}. This value is in bytes
* @param tx The transmitted traffic for the {@link com.wireguard.config.Peer} referenced by
* the provided {@link Key}. This value is in bytes.
* @param key A WireGuard public key bound to a particular peer
* @param rxBytes The received traffic for the {@link com.wireguard.config.Peer} referenced by
* the provided {@link Key}. This value is in bytes
* @param txBytes The transmitted traffic for the {@link com.wireguard.config.Peer} referenced by
* the provided {@link Key}. This value is in bytes.
* @param latestHandshake The timestamp of the latest handshake for the {@link com.wireguard.config.Peer}
* referenced by the provided {@link Key}. The value is in epoch milliseconds.
*/
void add(final Key key, final long rx, final long tx) {
peerBytes.put(key, Pair.create(rx, tx));
void add(final Key key, final long rxBytes, final long txBytes, final long latestHandshake) {
stats.put(key, new PeerStats(rxBytes, txBytes, latestHandshake));
lastTouched = SystemClock.elapsedRealtime();
}
@@ -49,31 +78,14 @@ public class Statistics {
}
/**
* Get the received traffic (in bytes) for the {@link com.wireguard.config.Peer} referenced by
* the provided {@link Key}
* Get the statistics for the {@link com.wireguard.config.Peer} referenced by the provided {@link Key}
*
* @param peer A {@link Key} representing a {@link com.wireguard.config.Peer}.
* @return a long representing the number of bytes received by this peer.
* @return a {@link PeerStats} representing various statistics about this peer.
*/
public long peerRx(final Key peer) {
final Pair<Long, Long> rxTx = peerBytes.get(peer);
if (rxTx == null)
return 0;
return rxTx.first;
}
/**
* Get the transmitted traffic (in bytes) for the {@link com.wireguard.config.Peer} referenced by
* the provided {@link Key}
*
* @param peer A {@link Key} representing a {@link com.wireguard.config.Peer}.
* @return a long representing the number of bytes transmitted by this peer.
*/
public long peerTx(final Key peer) {
final Pair<Long, Long> rxTx = peerBytes.get(peer);
if (rxTx == null)
return 0;
return rxTx.second;
@Nullable
public PeerStats peer(final Key peer) {
return this.stats.get(peer);
}
/**
@@ -83,7 +95,7 @@ public class Statistics {
* {@link com.wireguard.config.Peer}s
*/
public Key[] peers() {
return peerBytes.keySet().toArray(new Key[0]);
return stats.keySet().toArray(new Key[0]);
}
/**
@@ -93,8 +105,8 @@ public class Statistics {
*/
public long totalRx() {
long rx = 0;
for (final Pair<Long, Long> val : peerBytes.values()) {
rx += val.first;
for (final PeerStats val : stats.values()) {
rx += val.rxBytes;
}
return rx;
}
@@ -106,8 +118,8 @@ public class Statistics {
*/
public long totalTx() {
long tx = 0;
for (final Pair<Long, Long> val : peerBytes.values()) {
tx += val.second;
for (final PeerStats val : stats.values()) {
tx += val.txBytes;
}
return tx;
}
@@ -20,6 +20,7 @@ import com.wireguard.util.NonNullForAll;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -83,17 +84,17 @@ public final class WgQuickBackend implements Backend {
final Statistics stats = new Statistics();
final Collection<String> output = new ArrayList<>();
try {
if (rootShell.run(output, String.format("wg show '%s' transfer", tunnel.getName())) != 0)
if (rootShell.run(output, String.format("wg show '%s' dump", tunnel.getName())) != 0)
return stats;
} catch (final Exception ignored) {
return stats;
}
for (final String line : output) {
final String[] parts = line.split("\\t");
if (parts.length != 3)
if (parts.length != 8)
continue;
try {
stats.add(Key.fromBase64(parts[0]), Long.parseLong(parts[1]), Long.parseLong(parts[2]));
stats.add(Key.fromBase64(parts[0]), Long.parseLong(parts[5]), Long.parseLong(parts[6]), Long.parseLong(parts[4]) * 1000);
} catch (final Exception ignored) {
}
}
+4 -3
View File
@@ -6,15 +6,16 @@ cmake_minimum_required(VERSION 3.4.1)
project("WireGuard")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
add_link_options(LINKER:--build-id=none)
add_compile_options(-Wall -Werror)
add_executable(libwg-quick.so wireguard-tools/src/wg-quick/android.c ndk-compat/compat.c)
target_compile_options(libwg-quick.so PUBLIC -O3 -std=gnu11 -Wall -include ${CMAKE_CURRENT_SOURCE_DIR}/ndk-compat/compat.h -DWG_PACKAGE_NAME=\"${ANDROID_PACKAGE_NAME}\")
target_compile_options(libwg-quick.so PUBLIC -std=gnu11 -include ${CMAKE_CURRENT_SOURCE_DIR}/ndk-compat/compat.h -DWG_PACKAGE_NAME=\"${ANDROID_PACKAGE_NAME}\")
target_link_libraries(libwg-quick.so -ldl)
file(GLOB WG_SOURCES wireguard-tools/src/*.c ndk-compat/compat.c)
add_executable(libwg.so ${WG_SOURCES})
target_include_directories(libwg.so PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/wireguard-tools/src/uapi/linux/" "${CMAKE_CURRENT_SOURCE_DIR}/wireguard-tools/src/")
target_compile_options(libwg.so PUBLIC -O3 -std=gnu11 -D_GNU_SOURCE -include ${CMAKE_CURRENT_SOURCE_DIR}/ndk-compat/compat.h -DHAVE_VISIBILITY_HIDDEN -DRUNSTATEDIR=\"/data/data/${ANDROID_PACKAGE_NAME}/cache\")
target_compile_options(libwg.so PUBLIC -std=gnu11 -include ${CMAKE_CURRENT_SOURCE_DIR}/ndk-compat/compat.h -DRUNSTATEDIR=\"/data/data/${ANDROID_PACKAGE_NAME}/cache\")
add_custom_target(libwg-go.so WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/libwg-go" COMMENT "Building wireguard-go" VERBATIM COMMAND "${ANDROID_HOST_PREBUILTS}/bin/make"
ANDROID_ARCH_NAME=${ANDROID_ARCH_NAME}
@@ -31,7 +32,7 @@ add_custom_target(libwg-go.so WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/lib
# Strip unwanted ELF sections to prevent DT_FLAGS_1 warnings on old Android versions
file(GLOB ELF_CLEANER_SOURCES elf-cleaner/*.c elf-cleaner/*.cpp)
add_custom_target(elf-cleaner COMMENT "Building elf-cleaner" VERBATIM COMMAND "${CMAKE_C_COMPILER}"
add_custom_target(elf-cleaner COMMENT "Building elf-cleaner" VERBATIM COMMAND cc
-O2 -DPACKAGE_NAME="elf-cleaner" -DPACKAGE_VERSION="" -DCOPYRIGHT=""
-o "${CMAKE_CURRENT_BINARY_DIR}/elf-cleaner" ${ELF_CLEANER_SOURCES}
)
+5 -5
View File
@@ -20,12 +20,12 @@ export GOARCH := $(NDK_GO_ARCH_MAP_$(ANDROID_ARCH_NAME))
export GOOS := android
export CGO_ENABLED := 1
GO_VERSION := 1.20.2
GO_VERSION := 1.20.3
GO_PLATFORM := $(shell uname -s | tr '[:upper:]' '[:lower:]')-$(NDK_GO_ARCH_MAP_$(shell uname -m))
GO_TARBALL := go$(GO_VERSION).$(GO_PLATFORM).tar.gz
GO_HASH_darwin-amd64 := c93b8ced9517d07e1cd4c362c6e2d5242cb139e29b417a328fbf19aded08764c
GO_HASH_darwin-arm64 := 7343c87f19e79c0063532e82e1c4d6f42175a32d99f7a4d15e658e88bf97f885
GO_HASH_linux-amd64 := 4eaea32f59cde4dc635fbc42161031d13e1c780b87097f4b4234cfce671f1768
GO_HASH_darwin-amd64 := c1e1161d6d859deb576e6cfabeb40e3d042ceb1c6f444f617c3c9d76269c3565
GO_HASH_darwin-arm64 := 86b0ed0f2b2df50fa8036eea875d1cf2d76cefdacf247c44639a1464b7e36b95
GO_HASH_linux-amd64 := 979694c2c25c735755bf26f4f45e19e64e4811d661dd07b8c010f7a8e18adfca
default: $(DESTDIR)/libwg-go.so
@@ -47,6 +47,6 @@ $(BUILDDIR)/go-$(GO_VERSION)/.prepared: $(GRADLE_USER_HOME)/caches/golang/$(GO_T
$(DESTDIR)/libwg-go.so: export PATH := $(BUILDDIR)/go-$(GO_VERSION)/bin/:$(PATH)
$(DESTDIR)/libwg-go.so: $(BUILDDIR)/go-$(GO_VERSION)/.prepared go.mod
go build -tags linux -ldflags="-X golang.zx2c4.com/wireguard/ipc.socketDirectory=/data/data/$(ANDROID_PACKAGE_NAME)/cache/wireguard -buildid=" -v -trimpath -o "$@" -buildmode c-shared
go build -tags linux -ldflags="-X golang.zx2c4.com/wireguard/ipc.socketDirectory=/data/data/$(ANDROID_PACKAGE_NAME)/cache/wireguard -buildid=" -v -trimpath -buildvcs=false -o "$@" -buildmode c-shared
.DELETE_ON_ERROR:
-52
View File
@@ -6,58 +6,6 @@
#define FILE_IS_EMPTY
#if defined(__ANDROID_MIN_SDK_VERSION__) && __ANDROID_MIN_SDK_VERSION__ < 18
#undef FILE_IS_EMPTY
#include <stdio.h>
#include <stdlib.h>
ssize_t getdelim(char **buf, size_t *bufsiz, int delimiter, FILE *fp)
{
char *ptr, *eptr;
if (*buf == NULL || *bufsiz == 0) {
*bufsiz = BUFSIZ;
if ((*buf = malloc(*bufsiz)) == NULL)
return -1;
}
for (ptr = *buf, eptr = *buf + *bufsiz;;) {
int c = fgetc(fp);
if (c == -1) {
if (feof(fp)) {
ssize_t diff = (ssize_t)(ptr - *buf);
if (diff != 0) {
*ptr = '\0';
return diff;
}
}
return -1;
}
*ptr++ = c;
if (c == delimiter) {
*ptr = '\0';
return ptr - *buf;
}
if (ptr + 2 >= eptr) {
char *nbuf;
size_t nbufsiz = *bufsiz * 2;
ssize_t d = ptr - *buf;
if ((nbuf = realloc(*buf, nbufsiz)) == NULL)
return -1;
*buf = nbuf;
*bufsiz = nbufsiz;
eptr = nbuf + nbufsiz;
ptr = nbuf + d;
}
}
}
ssize_t getline(char **buf, size_t *bufsiz, FILE *fp)
{
return getdelim(buf, bufsiz, '\n', fp);
}
#endif
#if defined(__ANDROID_MIN_SDK_VERSION__) && __ANDROID_MIN_SDK_VERSION__ < 24
#undef FILE_IS_EMPTY
#include <string.h>
-6
View File
@@ -4,12 +4,6 @@
*
*/
#if defined(__ANDROID_MIN_SDK_VERSION__) && __ANDROID_MIN_SDK_VERSION__ < 18
#include <stdio.h>
ssize_t getdelim(char **buf, size_t *bufsiz, int delimiter, FILE *fp);
ssize_t getline(char **buf, size_t *bufsiz, FILE *fp);
#endif
#if defined(__ANDROID_MIN_SDK_VERSION__) && __ANDROID_MIN_SDK_VERSION__ < 24
char *strchrnul(const char *s, int c);
#endif
-98
View File
@@ -1,98 +0,0 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'org.jetbrains.kotlin.kapt'
}
version wireguardVersionName
group groupName
// Create a variable called keystorePropertiesFile, and initialize it to your
// keystore.properties file, in the rootProject folder.
final def keystorePropertiesFile = rootProject.file("keystore.properties")
android {
compileSdk 33
buildFeatures {
buildConfig = true
dataBinding = true
viewBinding = true
}
namespace = 'com.wireguard.android'
defaultConfig {
applicationId 'com.wireguard.android'
minSdkVersion 21
targetSdkVersion 33
versionCode wireguardVersionCode
versionName wireguardVersionName
buildConfigField 'int', 'MIN_SDK_VERSION', "$minSdkVersion.apiLevel"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
coreLibraryDesugaringEnabled = true
}
if (keystorePropertiesFile.exists()) {
final def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile rootProject.file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}
}
buildTypes {
release {
if (keystorePropertiesFile.exists()) signingConfig signingConfigs.release
minifyEnabled true
proguardFiles "proguard-android-optimize.txt", "proguard-rules.pro"
packagingOptions {
exclude "DebugProbesKt.bin"
exclude "kotlin-tooling-metadata.json"
}
}
debug {
applicationIdSuffix ".debug"
versionNameSuffix "-debug"
}
}
lint {
disable 'LongLogTag'
warning 'MissingTranslation', 'ImpliedQuantity'
}
}
dependencies {
implementation project(":tunnel")
implementation "androidx.activity:activity-ktx:$activityVersion"
implementation "androidx.annotation:annotation:$annotationsVersion"
implementation "androidx.appcompat:appcompat:$appcompatVersion"
implementation "androidx.constraintlayout:constraintlayout:$constraintLayoutVersion"
implementation "androidx.coordinatorlayout:coordinatorlayout:$coordinatorLayoutVersion"
implementation "androidx.biometric:biometric:$biometricVersion"
implementation "androidx.core:core-ktx:$coreKtxVersion"
implementation "androidx.fragment:fragment-ktx:$fragmentVersion"
implementation "androidx.preference:preference-ktx:$preferenceVersion"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleRuntimeKtxVersion"
implementation "androidx.datastore:datastore-preferences:$datastoreVersion"
implementation "com.google.android.material:material:$materialComponentsVersion"
implementation "com.journeyapps:zxing-android-embedded:$zxingEmbeddedVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugarVersion"
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:unchecked'
options.deprecation = true
}
tasks.withType(KotlinCompile).configureEach {
kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8
}
+89
View File
@@ -0,0 +1,89 @@
@file:Suppress("UnstableApiUsage")
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
val pkg: String = providers.gradleProperty("wireguardPackageName").get()
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.kapt)
}
android {
compileSdk = 33
buildFeatures {
buildConfig = true
dataBinding = true
viewBinding = true
}
namespace = pkg
defaultConfig {
applicationId = pkg
minSdk = 21
targetSdk = 33
versionCode = providers.gradleProperty("wireguardVersionCode").get().toInt()
versionName = providers.gradleProperty("wireguardVersionName").get()
buildConfigField("int", "MIN_SDK_VERSION", minSdk.toString())
buildConfigField("boolean", "IS_GOOGLE_PLAY", false.toString())
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
isCoreLibraryDesugaringEnabled = true
}
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles("proguard-android-optimize.txt")
packaging {
resources {
excludes += "DebugProbesKt.bin"
excludes += "kotlin-tooling-metadata.json"
}
}
}
debug {
applicationIdSuffix = ".debug"
versionNameSuffix = "-debug"
}
create("googleplay") {
initWith(getByName("release"))
buildConfigField("boolean", "IS_GOOGLE_PLAY", true.toString())
}
}
lint {
disable += "LongLogTag"
warning += "MissingTranslation"
warning += "ImpliedQuantity"
}
}
dependencies {
implementation(project(":tunnel"))
implementation(libs.androidx.activity.ktx)
implementation(libs.androidx.annotation)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.coordinatorlayout)
implementation(libs.androidx.biometric)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.fragment.ktx)
implementation(libs.androidx.preference.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.datastore.preferences)
implementation(libs.google.material)
implementation(libs.zxing.android.embedded)
implementation(libs.kotlinx.coroutines.android)
coreLibraryDesugaring(libs.desugarJdkLibs)
}
tasks.withType<JavaCompile>().configureEach {
options.compilerArgs.add("-Xlint:unchecked")
options.isDeprecation = true
}
tasks.withType<KotlinCompile>().configureEach {
compilerOptions.jvmTarget.set(JvmTarget.JVM_17)
}
+1 -2
View File
@@ -1,7 +1,6 @@
-allowaccessmodification
-dontpreverify
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontobfuscate
-verbose
-keepattributes *Annotation*
-14
View File
@@ -1,14 +0,0 @@
# Squelch all warnings, they're harmless but ProGuard
# escalates them as errors.
-dontwarn sun.misc.Unsafe
# Fragment 1.2.4 allows Fragment classes to be obfuscated but
# databinding references in XML seem to not be rewritten to
# match, so we preserve the names as 1.2.3 did.
-if public class ** extends androidx.fragment.app.Fragment
-keep public class <1> {
public <init>();
}
# Don't obfuscate
-dontobfuscate
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" tools:node="remove" />
<application>
<receiver android:name=".updater.Updater$AppUpdatedReceiver" tools:node="remove" />
</application>
</manifest>
+7
View File
@@ -6,6 +6,7 @@
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"
@@ -106,6 +107,12 @@
</intent-filter>
</receiver>
<receiver android:name=".updater.Updater$AppUpdatedReceiver" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
</receiver>
<receiver
android:name=".model.TunnelManager$IntentReceiver"
android:permission="${applicationId}.permission.CONTROL_TUNNELS"
@@ -22,6 +22,7 @@ import com.wireguard.android.backend.GoBackend
import com.wireguard.android.backend.WgQuickBackend
import com.wireguard.android.configStore.FileConfigStore
import com.wireguard.android.model.TunnelManager
import com.wireguard.android.updater.Updater
import com.wireguard.android.util.RootShell
import com.wireguard.android.util.ToolsInstaller
import com.wireguard.android.util.UserKnobs
@@ -58,10 +59,6 @@ class Application : android.app.Application() {
startActivity(intent)
System.exit(0)
}
if (BuildConfig.DEBUG) {
StrictMode.setVmPolicy(VmPolicy.Builder().detectAll().penaltyLog().build())
StrictMode.setThreadPolicy(ThreadPolicy.Builder().detectAll().penaltyLog().build())
}
}
private suspend fun determineBackend(): Backend {
@@ -119,6 +116,12 @@ class Application : android.app.Application() {
Log.e(TAG, Log.getStackTraceString(e))
}
}
Updater.monitorForUpdates()
if (BuildConfig.DEBUG) {
StrictMode.setVmPolicy(VmPolicy.Builder().detectAll().penaltyLog().build())
StrictMode.setThreadPolicy(ThreadPolicy.Builder().detectAll().penaltyLog().build())
}
}
override fun onTerminate() {
@@ -14,9 +14,9 @@ import kotlinx.coroutines.launch
class BootShutdownReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val action = intent.action ?: return
applicationScope.launch {
if (Application.getBackend() !is WgQuickBackend) return@launch
val action = intent.action ?: return@launch
val tunnelManager = Application.getTunnelManager()
if (Intent.ACTION_BOOT_COMPLETED == action) {
Log.i(TAG, "Broadcast receiver restoring state (boot)")
@@ -157,9 +157,7 @@ class LogViewerActivity : AppCompatActivity() {
builder.append(rawLogLines[i])
builder.append('\n')
}
val ret = builder.toString().toByteArray(Charsets.UTF_8)
builder.clear()
return ret
return builder.toString().toByteArray(Charsets.UTF_8)
}
private suspend fun saveLog() {
@@ -203,7 +201,7 @@ class LogViewerActivity : AppCompatActivity() {
var priorModified = false
val bufferedLogLines = arrayListOf<LogLine>()
var timeout = 1000000000L / 2 // The timeout is initially small so that the view gets populated immediately.
val MAX_LINES = (1 shl 17) - 1
val MAX_LINES = (1 shl 16) - 1
val MAX_BUFFERED_LINES = (1 shl 14) - 1
while (true) {
@@ -63,8 +63,8 @@ class AppListDialogFragment : DialogFragment() {
withContext(Dispatchers.Main.immediate) {
appData.clear()
appData.addAll(applicationData)
setButtonText()
}
setButtonText()
} catch (e: Throwable) {
withContext(Dispatchers.Main.immediate) {
val error = ErrorMessages[e]
@@ -112,16 +112,25 @@ class TunnelDetailFragment : BaseFragment(), MenuProvider {
val peer: TunnelDetailPeerBinding = DataBindingUtil.getBinding(binding.peersLayout.getChildAt(i))
?: continue
val publicKey = peer.item!!.publicKey
val rx = statistics.peerRx(publicKey)
val tx = statistics.peerTx(publicKey)
if (rx == 0L && tx == 0L) {
val peerStats = statistics.peer(publicKey)
if (peerStats == null || (peerStats.rxBytes == 0L && peerStats.txBytes == 0L)) {
peer.transferLabel.visibility = View.GONE
peer.transferText.visibility = View.GONE
continue
} else {
peer.transferText.text = getString(R.string.transfer_rx_tx,
QuantityFormatter.formatBytes(peerStats.rxBytes),
QuantityFormatter.formatBytes(peerStats.txBytes))
peer.transferLabel.visibility = View.VISIBLE
peer.transferText.visibility = View.VISIBLE
}
if (peerStats == null || peerStats.latestHandshakeEpochMillis == 0L) {
peer.latestHandshakeLabel.visibility = View.GONE
peer.latestHandshakeText.visibility = View.GONE
} else {
peer.latestHandshakeText.text = QuantityFormatter.formatEpochAgo(peerStats.latestHandshakeEpochMillis)
peer.latestHandshakeLabel.visibility = View.VISIBLE
peer.latestHandshakeText.visibility = View.VISIBLE
}
peer.transferText.text = getString(R.string.transfer_rx_tx, QuantityFormatter.formatBytes(rx), QuantityFormatter.formatBytes(tx))
peer.transferLabel.visibility = View.VISIBLE
peer.transferText.visibility = View.VISIBLE
}
} catch (e: Throwable) {
for (i in 0 until binding.peersLayout.childCount) {
@@ -129,6 +138,8 @@ class TunnelDetailFragment : BaseFragment(), MenuProvider {
?: continue
peer.transferLabel.visibility = View.GONE
peer.transferText.visibility = View.GONE
peer.latestHandshakeLabel.visibility = View.GONE
peer.latestHandshakeText.visibility = View.GONE
}
}
}
@@ -33,6 +33,7 @@ import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter.RowC
import com.wireguard.android.databinding.TunnelListFragmentBinding
import com.wireguard.android.databinding.TunnelListItemBinding
import com.wireguard.android.model.ObservableTunnel
import com.wireguard.android.updater.SnackbarUpdateShower
import com.wireguard.android.util.ErrorMessages
import com.wireguard.android.util.QrCodeFromFileScanner
import com.wireguard.android.util.TunnelImporter
@@ -122,6 +123,8 @@ class TunnelListFragment : BaseFragment() {
backPressedCallback = requireActivity().onBackPressedDispatcher.addCallback(this) { actionMode?.finish() }
backPressedCallback?.isEnabled = false
SnackbarUpdateShower.attachToActivity(requireActivity(), binding?.mainContainer!!, binding?.createFab)
return binding?.root
}
@@ -8,11 +8,11 @@ package com.wireguard.android.preference
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.util.AttributeSet
import android.widget.Toast
import androidx.preference.Preference
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.wireguard.android.BuildConfig
import com.wireguard.android.R
import com.wireguard.android.util.ErrorMessages
@@ -22,21 +22,8 @@ class DonatePreference(context: Context, attrs: AttributeSet?) : Preference(cont
override fun getTitle() = context.getString(R.string.donate_title)
override fun onClick() {
val installer = try {
val packageName = context.packageName
val pm = context.packageManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
pm.getInstallSourceInfo(packageName).installingPackageName ?: ""
} else {
@Suppress("DEPRECATION")
pm.getInstallerPackageName(packageName) ?: ""
}
} catch (_: Throwable) {
""
}
/* Google Play Store forbids links to our donation page. */
if (installer == "com.android.vending") {
if (BuildConfig.IS_GOOGLE_PLAY) {
MaterialAlertDialogBuilder(context)
.setTitle(R.string.donate_title)
.setMessage(R.string.donate_google_play_disappointment)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,160 @@
/*
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.updater
import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
import com.wireguard.android.BuildConfig
import com.wireguard.android.R
import com.wireguard.android.util.ErrorMessages
import com.wireguard.android.util.QuantityFormatter
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.seconds
object SnackbarUpdateShower {
private class SwapableSnackbar(activity: FragmentActivity, view: View, anchor: View?) {
val actionSnackbar = makeSnackbar(activity, view, anchor)
val statusSnackbar = makeSnackbar(activity, view, anchor)
var showingAction: Boolean = false
var showingStatus: Boolean = false
private fun makeSnackbar(activity: FragmentActivity, view: View, anchor: View?): Snackbar {
val snackbar = Snackbar.make(activity, view, "", Snackbar.LENGTH_INDEFINITE)
if (anchor != null)
snackbar.anchorView = anchor
snackbar.setTextMaxLines(6)
snackbar.behavior = object : BaseTransientBottomBar.Behavior() {
override fun canSwipeDismissView(child: View): Boolean {
return false
}
}
snackbar.addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
override fun onDismissed(snackbar: Snackbar?, @DismissEvent event: Int) {
super.onDismissed(snackbar, event)
if (event == DISMISS_EVENT_MANUAL || event == DISMISS_EVENT_ACTION ||
(snackbar == actionSnackbar && !showingAction) ||
(snackbar == statusSnackbar && !showingStatus)
)
return
activity.lifecycleScope.launch {
delay(5.seconds)
snackbar?.show()
}
}
})
return snackbar
}
fun showAction(text: String, action: String, listener: View.OnClickListener) {
if (showingStatus) {
showingStatus = false
statusSnackbar.dismiss()
}
actionSnackbar.setText(text)
actionSnackbar.setAction(action, listener)
if (!showingAction) {
actionSnackbar.show()
showingAction = true
}
}
fun showText(text: String) {
if (showingAction) {
showingAction = false
actionSnackbar.dismiss()
}
statusSnackbar.setText(text)
if (!showingStatus) {
statusSnackbar.show()
showingStatus = true
}
}
fun dismiss() {
actionSnackbar.dismiss()
statusSnackbar.dismiss()
showingAction = false
showingStatus = false
}
}
fun attachToActivity(activity: FragmentActivity, view: View, anchor: View?) {
if (BuildConfig.IS_GOOGLE_PLAY)
return
val snackbar = SwapableSnackbar(activity, view, anchor)
val context = activity.applicationContext
var lastUserIntervention: Updater.Progress.NeedsUserIntervention? = null
val intentLauncher = activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
lastUserIntervention?.markAsDone()
}
Updater.state.onEach { progress ->
when (progress) {
is Updater.Progress.Complete ->
snackbar.dismiss()
is Updater.Progress.Available ->
snackbar.showAction(
context.getString(R.string.updater_avalable),
context.getString(R.string.updater_action)
) {
progress.update()
}
is Updater.Progress.NeedsUserIntervention -> {
lastUserIntervention = progress
intentLauncher.launch(progress.intent)
}
is Updater.Progress.Installing ->
snackbar.showText(context.getString(R.string.updater_installing))
is Updater.Progress.Rechecking ->
snackbar.showText(context.getString(R.string.updater_rechecking))
is Updater.Progress.Downloading -> {
if (progress.bytesTotal != 0UL) {
snackbar.showText(
context.getString(
R.string.updater_download_progress,
QuantityFormatter.formatBytes(progress.bytesDownloaded.toLong()),
QuantityFormatter.formatBytes(progress.bytesTotal.toLong()),
progress.bytesDownloaded.toFloat() * 100.0 / progress.bytesTotal.toFloat()
)
)
} else {
snackbar.showText(
context.getString(
R.string.updater_download_progress_nototal,
QuantityFormatter.formatBytes(progress.bytesDownloaded.toLong())
)
)
}
}
is Updater.Progress.Failure -> {
snackbar.showText(
context.getString(
R.string.updater_failure,
ErrorMessages[progress.error]
)
)
delay(5.seconds)
progress.retry()
}
}
}.launchIn(activity.lifecycleScope)
}
}
@@ -0,0 +1,434 @@
/*
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.updater
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageInstaller
import android.os.Build
import android.util.Base64
import android.util.Log
import androidx.core.content.ContextCompat
import androidx.core.content.IntentCompat
import com.wireguard.android.Application
import com.wireguard.android.BuildConfig
import com.wireguard.android.activity.MainActivity
import com.wireguard.android.util.UserKnobs
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.IOException
import java.net.HttpURLConnection
import java.net.URL
import java.nio.charset.StandardCharsets
import java.security.InvalidKeyException
import java.security.InvalidParameterException
import java.security.MessageDigest
import java.util.UUID
import kotlin.math.max
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
object Updater {
private const val TAG = "WireGuard/Updater"
private const val LATEST_VERSION_URL =
"https://download.wireguard.com/android-client/latest.sig"
private const val APK_PATH_URL = "https://download.wireguard.com/android-client/%s"
private val APK_NAME_PREFIX = BuildConfig.APPLICATION_ID.removeSuffix(".debug") + "-"
private const val APK_NAME_SUFFIX = ".apk"
private const val RELEASE_PUBLIC_KEY_BASE64 =
"RWTAzwGRYr3EC9px0Ia3fbttz8WcVN6wrOwWp2delz4el6SI8XmkKSMp"
private val CURRENT_VERSION = Version(BuildConfig.VERSION_NAME.removeSuffix("-debug"))
private val updaterScope = CoroutineScope(Job() + Dispatchers.IO)
sealed class Progress {
object Complete : Progress()
class Available(val version: String) : Progress() {
fun update() {
Application.getCoroutineScope().launch {
UserKnobs.setUpdaterNewerVersionConsented(version)
}
}
}
object Rechecking : Progress()
class Downloading(val bytesDownloaded: ULong, val bytesTotal: ULong) : Progress()
object Installing : Progress()
class NeedsUserIntervention(val intent: Intent, private val id: Int) : Progress() {
private suspend fun installerActive(): Boolean {
if (mutableState.firstOrNull() != this@NeedsUserIntervention)
return true
try {
if (Application.get().packageManager.packageInstaller.getSessionInfo(id)?.isActive == true)
return true
} catch (_: SecurityException) {
return true
}
return false
}
fun markAsDone() {
Application.getCoroutineScope().launch {
if (installerActive())
return@launch
delay(7.seconds)
if (installerActive())
return@launch
emitProgress(Failure(Exception("Ignored by user")))
}
}
}
class Failure(val error: Throwable) : Progress() {
fun retry() {
updaterScope.launch {
downloadAndUpdateWrapErrors()
}
}
}
}
private val mutableState = MutableStateFlow<Progress>(Progress.Complete)
val state = mutableState.asStateFlow()
private suspend fun emitProgress(progress: Progress, force: Boolean = false) {
if (force || mutableState.firstOrNull()?.javaClass != progress.javaClass)
mutableState.emit(progress)
}
private class Sha256Digest(hex: String) {
val bytes: ByteArray
init {
if (hex.length != 64)
throw InvalidParameterException("SHA256 hashes must be 32 bytes long")
bytes = hex.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
}
}
@OptIn(ExperimentalUnsignedTypes::class)
private class Version(version: String) : Comparable<Version> {
val parts: ULongArray
init {
val strParts = version.split(".")
if (strParts.isEmpty())
throw InvalidParameterException("Version has no parts")
parts = ULongArray(strParts.size)
for (i in parts.indices) {
parts[i] = strParts[i].toULong()
}
}
override fun toString(): String {
return parts.joinToString(".")
}
override fun compareTo(other: Version): Int {
for (i in 0 until max(parts.size, other.parts.size)) {
val lhsPart = if (i < parts.size) parts[i] else 0UL
val rhsPart = if (i < other.parts.size) other.parts[i] else 0UL
if (lhsPart > rhsPart)
return 1
else if (lhsPart < rhsPart)
return -1
}
return 0
}
}
private class Update(val fileName: String, val version: Version, val hash: Sha256Digest)
private fun versionOfFile(name: String): Version? {
if (!name.startsWith(APK_NAME_PREFIX) || !name.endsWith(APK_NAME_SUFFIX))
return null
return try {
Version(name.substring(APK_NAME_PREFIX.length, name.length - APK_NAME_SUFFIX.length))
} catch (_: Throwable) {
null
}
}
private fun verifySignedFileList(signifyDigest: String): List<Update> {
val updates = ArrayList<Update>(1)
val publicKeyBytes = Base64.decode(RELEASE_PUBLIC_KEY_BASE64, Base64.DEFAULT)
if (publicKeyBytes == null || publicKeyBytes.size != 32 + 10 || publicKeyBytes[0] != 'E'.code.toByte() || publicKeyBytes[1] != 'd'.code.toByte())
throw InvalidKeyException("Invalid public key")
val lines = signifyDigest.split("\n", limit = 3)
if (lines.size != 3)
throw InvalidParameterException("Invalid signature format: too few lines")
if (!lines[0].startsWith("untrusted comment: "))
throw InvalidParameterException("Invalid signature format: missing comment")
val signatureBytes = Base64.decode(lines[1], Base64.DEFAULT)
if (signatureBytes == null || signatureBytes.size != 64 + 10)
throw InvalidParameterException("Invalid signature format: wrong sized or missing signature")
for (i in 0..9) {
if (signatureBytes[i] != publicKeyBytes[i])
throw InvalidParameterException("Invalid signature format: wrong signer")
}
if (!Ed25519.verify(
lines[2].toByteArray(StandardCharsets.UTF_8),
signatureBytes.sliceArray(10 until 10 + 64),
publicKeyBytes.sliceArray(10 until 10 + 32)
)
)
throw SecurityException("Invalid signature")
for (line in lines[2].split("\n").dropLastWhile { it.isEmpty() }) {
val components = line.split(" ", limit = 2)
if (components.size != 2)
throw InvalidParameterException("Invalid file list format: too few components")
/* If version is null, it's not a file we understand, but still a legitimate entry, so don't throw. */
val version = versionOfFile(components[1]) ?: continue
updates.add(Update(components[1], version, Sha256Digest(components[0])))
}
return updates
}
private fun checkForUpdates(): Update? {
val connection = URL(LATEST_VERSION_URL).openConnection() as HttpURLConnection
connection.setRequestProperty("User-Agent", Application.USER_AGENT)
connection.connect()
if (connection.responseCode != HttpURLConnection.HTTP_OK)
throw IOException(connection.responseMessage)
var fileListBytes = ByteArray(1024 * 512 /* 512 KiB */)
connection.inputStream.use {
val len = it.read(fileListBytes)
if (len <= 0)
throw IOException("File list is empty")
fileListBytes = fileListBytes.sliceArray(0 until len)
}
return verifySignedFileList(fileListBytes.decodeToString()).maxByOrNull { it.version }
}
private suspend fun downloadAndUpdate() = withContext(Dispatchers.IO) {
val receiver = InstallReceiver()
val context = Application.get().applicationContext
val pendingIntent = withContext(Dispatchers.Main) {
ContextCompat.registerReceiver(
context,
receiver,
IntentFilter(receiver.sessionId),
ContextCompat.RECEIVER_NOT_EXPORTED
)
PendingIntent.getBroadcast(
context,
0,
Intent(receiver.sessionId).setPackage(context.packageName),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)
}
emitProgress(Progress.Rechecking)
val update = checkForUpdates()
if (update == null || update.version <= CURRENT_VERSION) {
emitProgress(Progress.Complete)
return@withContext
}
emitProgress(Progress.Downloading(0UL, 0UL), true)
val connection =
URL(APK_PATH_URL.format(update.fileName)).openConnection() as HttpURLConnection
connection.setRequestProperty("User-Agent", Application.USER_AGENT)
connection.connect()
if (connection.responseCode != HttpURLConnection.HTTP_OK)
throw IOException("Update could not be fetched: ${connection.responseCode}")
var downloadedByteLen: ULong = 0UL
val totalByteLen =
(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) connection.contentLengthLong else connection.contentLength).toLong()
.toULong()
val fileBytes = ByteArray(1024 * 32 /* 32 KiB */)
val digest = MessageDigest.getInstance("SHA-256")
emitProgress(Progress.Downloading(downloadedByteLen, totalByteLen), true)
val installer = context.packageManager.packageInstaller
val params =
PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
params.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED)
params.setAppPackageName(context.packageName) /* Enforces updates; disallows new apps. */
val session = installer.openSession(installer.createSession(params))
var sessionFailure = true
try {
val installDest = session.openWrite(receiver.sessionId, 0, -1)
installDest.use { dest ->
connection.inputStream.use { src ->
while (true) {
val readLen = src.read(fileBytes)
if (readLen <= 0)
break
digest.update(fileBytes, 0, readLen)
dest.write(fileBytes, 0, readLen)
downloadedByteLen += readLen.toUInt()
emitProgress(Progress.Downloading(downloadedByteLen, totalByteLen), true)
if (downloadedByteLen >= 1024UL * 1024UL * 100UL /* 100 MiB */)
throw IOException("File too large")
}
}
}
emitProgress(Progress.Installing)
if (!digest.digest().contentEquals(update.hash.bytes))
throw SecurityException("Update has invalid hash")
sessionFailure = false
} finally {
if (sessionFailure) {
session.abandon()
session.close()
}
}
session.commit(pendingIntent.intentSender)
session.close()
}
private suspend fun downloadAndUpdateWrapErrors() {
try {
downloadAndUpdate()
} catch (e: Throwable) {
Log.e(TAG, "Update failure", e)
emitProgress(Progress.Failure(e))
}
}
private class InstallReceiver : BroadcastReceiver() {
val sessionId = UUID.randomUUID().toString()
override fun onReceive(context: Context, intent: Intent) {
if (sessionId != intent.action)
return
when (val status =
intent.getIntExtra(
PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE_INVALID
)) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
val id = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, 0)
val userIntervention = IntentCompat.getParcelableExtra(
intent,
Intent.EXTRA_INTENT,
Intent::class.java
)!!
Application.getCoroutineScope().launch {
emitProgress(Progress.NeedsUserIntervention(userIntervention, id))
}
}
PackageInstaller.STATUS_SUCCESS -> {
Application.getCoroutineScope().launch {
emitProgress(Progress.Complete)
}
context.applicationContext.unregisterReceiver(this)
}
else -> {
val id = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, 0)
try {
context.applicationContext.packageManager.packageInstaller.abandonSession(id)
} catch (_: SecurityException) {
}
val message =
intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
?: "Installation error $status"
Application.getCoroutineScope().launch {
val e = Exception(message)
Log.e(TAG, "Update failure", e)
emitProgress(Progress.Failure(e))
}
context.applicationContext.unregisterReceiver(this)
}
}
}
}
fun monitorForUpdates() {
if (BuildConfig.IS_GOOGLE_PLAY)
return
updaterScope.launch {
if (UserKnobs.updaterNewerVersionSeen.firstOrNull()
?.let { Version(it) > CURRENT_VERSION } == true
)
return@launch
var waitTime = 15
while (true) {
try {
val update = checkForUpdates() ?: continue
if (update.version > CURRENT_VERSION) {
Log.i(TAG, "Update available: ${update.version}")
UserKnobs.setUpdaterNewerVersionSeen(update.version.toString())
return@launch
}
} catch (_: Throwable) {
}
delay(waitTime.minutes)
waitTime = 45
}
}
UserKnobs.updaterNewerVersionSeen.onEach { ver ->
if (ver != null && Version(ver) > CURRENT_VERSION && UserKnobs.updaterNewerVersionConsented.firstOrNull()
?.let { Version(it) > CURRENT_VERSION } != true
)
emitProgress(Progress.Available(ver))
}.launchIn(Application.getCoroutineScope())
UserKnobs.updaterNewerVersionConsented.onEach { ver ->
if (ver != null && Version(ver) > CURRENT_VERSION)
updaterScope.launch {
downloadAndUpdateWrapErrors()
}
}.launchIn(Application.getCoroutineScope())
}
class AppUpdatedReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (BuildConfig.IS_GOOGLE_PLAY)
return
if (intent.action != Intent.ACTION_MY_PACKAGE_REPLACED)
return
val installer = try {
val packageName = context.packageName
val pm = context.packageManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
pm.getInstallSourceInfo(packageName).installingPackageName ?: ""
} else {
@Suppress("DEPRECATION")
pm.getInstallerPackageName(packageName) ?: ""
}
} catch (_: Throwable) {
""
}
if (installer != context.packageName)
return
/* TODO: does not work because of restrictions placed on broadcast receivers. */
val start = Intent(context, MainActivity::class.java)
start.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
start.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(start)
}
}
}
@@ -92,8 +92,8 @@ object ErrorMessages {
rootCause is ChecksumException -> {
resources.getString(R.string.error_qr_checksum)
}
rootCause.message != null -> {
rootCause.message!!
rootCause.localizedMessage != null -> {
rootCause.localizedMessage!!
}
else -> {
val errorType = rootCause.javaClass.simpleName
@@ -109,7 +109,7 @@ object ErrorMessages {
if (kfe!!.type == KeyFormatException.Type.LENGTH) return resources.getString(KFE_FORMAT_MAP.getValue(kfe.format))
} else if (bce.cause is ParseException) {
val pe = bce.cause as ParseException?
if (pe!!.message != null) return ": ${pe.message}"
if (pe!!.localizedMessage != null) return ": ${pe.localizedMessage}"
} else if (bce.location == BadConfigException.Location.LISTEN_PORT) {
return resources.getString(R.string.bad_config_explanation_udp_port)
} else if (bce.location == BadConfigException.Location.MTU) {
@@ -5,8 +5,17 @@
package com.wireguard.android.util
import android.icu.text.ListFormatter
import android.icu.text.MeasureFormat
import android.icu.text.RelativeDateTimeFormatter
import android.icu.util.Measure
import android.icu.util.MeasureUnit
import android.os.Build
import com.wireguard.android.Application
import com.wireguard.android.R
import java.util.Locale
import kotlin.time.Duration.Companion.seconds
import kotlin.time.DurationUnit
object QuantityFormatter {
fun formatBytes(bytes: Long): String {
@@ -19,4 +28,40 @@ object QuantityFormatter {
else -> context.getString(R.string.transfer_tibibytes, bytes / (1024.0 * 1024.0 * 1024.0) / 1024.0)
}
}
fun formatEpochAgo(epochMillis: Long): String {
var span = (System.currentTimeMillis() - epochMillis) / 1000
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
return Application.get().applicationContext.getString(R.string.latest_handshake_ago, span.seconds.toString())
if (span <= 0L)
return RelativeDateTimeFormatter.getInstance().format(RelativeDateTimeFormatter.Direction.PLAIN, RelativeDateTimeFormatter.AbsoluteUnit.NOW)
val measureFormat = MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE)
val parts = ArrayList<CharSequence>(4)
if (span >= 24 * 60 * 60L) {
val v = span / (24 * 60 * 60L)
parts.add(measureFormat.format(Measure(v, MeasureUnit.DAY)))
span -= v * (24 * 60 * 60L)
}
if (span >= 60 * 60L) {
val v = span / (60 * 60L)
parts.add(measureFormat.format(Measure(v, MeasureUnit.HOUR)))
span -= v * (60 * 60L)
}
if (span >= 60L) {
val v = span / 60L
parts.add(measureFormat.format(Measure(v, MeasureUnit.MINUTE)))
span -= v * 60L
}
if (span > 0L)
parts.add(measureFormat.format(Measure(span, MeasureUnit.SECOND)))
val joined = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
parts.joinToString()
else
ListFormatter.getInstance(Locale.getDefault(), ListFormatter.Type.UNITS, ListFormatter.Width.SHORT).format(parts)
return Application.get().applicationContext.getString(R.string.latest_handshake_ago, joined)
}
}
@@ -88,4 +88,34 @@ object UserKnobs {
it[RUNNING_TUNNELS] = runningTunnels
}
}
private val UPDATER_NEWER_VERSION_SEEN = stringPreferencesKey("updater_newer_version_seen")
val updaterNewerVersionSeen: Flow<String?>
get() = Application.getPreferencesDataStore().data.map {
it[UPDATER_NEWER_VERSION_SEEN]
}
suspend fun setUpdaterNewerVersionSeen(newerVersionSeen: String?) {
Application.getPreferencesDataStore().edit {
if (newerVersionSeen == null)
it.remove(UPDATER_NEWER_VERSION_SEEN)
else
it[UPDATER_NEWER_VERSION_SEEN] = newerVersionSeen
}
}
private val UPDATER_NEWER_VERSION_CONSENTED = stringPreferencesKey("updater_newer_version_consented")
val updaterNewerVersionConsented: Flow<String?>
get() = Application.getPreferencesDataStore().data.map {
it[UPDATER_NEWER_VERSION_CONSENTED]
}
suspend fun setUpdaterNewerVersionConsented(newerVersionConsented: String?) {
Application.getPreferencesDataStore().edit {
if (newerVersionConsented == null)
it.remove(UPDATER_NEWER_VERSION_CONSENTED)
else
it[UPDATER_NEWER_VERSION_CONSENTED] = newerVersionConsented
}
}
}
@@ -71,7 +71,7 @@
<TextView
android:id="@+id/interface_name_label"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:labelFor="@+id/interface_name_text"
@@ -81,7 +81,7 @@
<TextView
android:id="@+id/interface_name_text"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/name"
android:nextFocusUp="@id/tunnel_switch"
@@ -96,7 +96,7 @@
<TextView
android:id="@+id/public_key_label"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:labelFor="@+id/public_key_text"
@@ -106,7 +106,7 @@
<TextView
android:id="@+id/public_key_text"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/public_key"
android:ellipsize="end"
@@ -124,7 +124,7 @@
<TextView
android:id="@+id/addresses_label"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:labelFor="@+id/addresses_text"
@@ -135,7 +135,7 @@
<TextView
android:id="@+id/addresses_text"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/addresses"
android:nextFocusUp="@id/public_key_text"
@@ -151,7 +151,7 @@
<TextView
android:id="@+id/dns_servers_label"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:labelFor="@+id/dns_servers_text"
@@ -162,7 +162,7 @@
<TextView
android:id="@+id/dns_servers_text"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/dns_servers"
android:nextFocusUp="@id/addresses_text"
@@ -178,7 +178,7 @@
<TextView
android:id="@+id/dns_search_domains_label"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:labelFor="@+id/dns_search_domain_text"
@@ -189,7 +189,7 @@
<TextView
android:id="@+id/dns_search_domains_text"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/dns_search_domains"
android:nextFocusUp="@id/dns_servers_text"
@@ -277,7 +277,7 @@
<TextView
android:id="@+id/applications_label"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:labelFor="@+id/applications_text"
@@ -288,7 +288,7 @@
<TextView
android:id="@+id/applications_text"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/applications"
android:nextFocusUp="@id/mtu_text"
+42 -14
View File
@@ -23,7 +23,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/peer_title"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/peer"
android:textAppearance="?attr/textAppearanceTitleMedium"
@@ -32,7 +32,7 @@
<TextView
android:id="@+id/public_key_label"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:labelFor="@+id/public_key_text"
@@ -42,7 +42,7 @@
<TextView
android:id="@+id/public_key_text"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/public_key"
android:ellipsize="end"
@@ -59,7 +59,7 @@
<TextView
android:id="@+id/pre_shared_key_label"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:labelFor="@+id/pre_shared_key_text"
@@ -70,7 +70,7 @@
<TextView
android:id="@+id/pre_shared_key_text"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/pre_shared_key"
android:ellipsize="end"
@@ -88,7 +88,7 @@
<TextView
android:id="@+id/allowed_ips_label"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:labelFor="@+id/allowed_ips_text"
@@ -99,7 +99,7 @@
<TextView
android:id="@+id/allowed_ips_text"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/allowed_ips"
android:nextFocusUp="@id/pre_shared_key_text"
@@ -115,7 +115,7 @@
<TextView
android:id="@+id/endpoint_label"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:labelFor="@+id/endpoint_text"
@@ -126,7 +126,7 @@
<TextView
android:id="@+id/endpoint_text"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/endpoint"
android:nextFocusUp="@id/allowed_ips_text"
@@ -142,7 +142,7 @@
<TextView
android:id="@+id/persistent_keepalive_label"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:labelFor="@+id/persistent_keepalive_text"
@@ -153,7 +153,7 @@
<TextView
android:id="@+id/persistent_keepalive_text"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/persistent_keepalive"
android:nextFocusUp="@id/endpoint_text"
@@ -169,9 +169,9 @@
<TextView
android:id="@+id/transfer_label"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/endpoint_text"
android:layout_below="@+id/persistent_keepalive_text"
android:layout_marginTop="8dp"
android:labelFor="@+id/transfer_text"
android:text="@string/transfer"
@@ -182,7 +182,7 @@
<TextView
android:id="@+id/transfer_text"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/transfer_label"
android:contentDescription="@string/transfer"
@@ -194,6 +194,34 @@
app:layout_constraintTop_toBottomOf="@+id/transfer_label"
tools:text="1024 MB"
tools:visibility="visible" />
<TextView
android:id="@+id/latest_handshake_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/transfer_text"
android:layout_marginTop="8dp"
android:labelFor="@+id/latest_handshake_text"
android:text="@string/latest_handshake"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/transfer_text"
tools:visibility="visible" />
<TextView
android:id="@+id/latest_handshake_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/latest_handshake_label"
android:contentDescription="@string/latest_handshake"
android:nextFocusUp="@id/transfer_text"
android:onClick="@{ClipboardUtils::copyTextView}"
android:textAppearance="?attr/textAppearanceBodyLarge"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/latest_handshake_label"
tools:text="4 minutes, 27 seconds ago"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</layout>
@@ -111,6 +111,8 @@
<string name="tv_select_a_storage_drive">تحديد محرك تخزين</string>
<string name="tv_no_file_picker">الرجاء تثبيت مدير ملفات لتتمكن من تصفح الملفات</string>
<string name="tv_add_tunnel_get_started">أضف نفقاً لتبدأ</string>
<string name="donate_title">♥️ تبرع لمشروع وايرجارد</string>
<string name="donate_summary">كل مساهمة تساعد</string>
<string name="disable_config_export_title">تعطيل تصدير التكوين</string>
<string name="disable_config_export_description">تعطيل تصدير الإعدادات يجعل المفاتيح الخاصة غير متاحة</string>
<string name="dns_servers">خوادم DNS</string>
@@ -120,6 +122,7 @@
<string name="error_down">خطأ في فصل النفق:%s</string>
<string name="error_fetching_apps">خطأ في جلب قائمة التطبيقات: %s</string>
<string name="error_root">الرجاء الحصول على صلاحيات الروت وحاول مرة أخرى</string>
<string name="error_prepare">خطأ في تحضير النفق: %s</string>
<string name="error_up">خطأ خلال إنشاء النفق: %s</string>
<string name="exclude_private_ips">استبعاد عناوين بروتوكول الإنترنت (IP) الخاصة</string>
<string name="generate_new_private_key">توليد مفتاح خاص جديد</string>
@@ -215,6 +218,7 @@
<string name="tunnel_create_success">تم إنشاء نفق \"%s\" بنجاح</string>
<string name="tunnel_error_already_exists">النفق \"%s\" موجود بالفعل</string>
<string name="tunnel_error_invalid_name">إسم غير صالح</string>
<string name="tunnel_list_placeholder">أضف نفق باستخدام الزر أدناه</string>
<string name="tunnel_name">إسم النفق</string>
<string name="tunnel_on_error">غير قادر على تشغيل النفق (wgTurnOn أعاد %d)</string>
<string name="tunnel_dns_failure">غير قادر على حل اسم مضيف نظام أسماء النطاقات (DNS hostname): \"%s\"</string>
@@ -72,6 +72,8 @@
<string name="config_file_exists_error">Konfigurationsfilen \"%s\" findes allerede</string>
<string name="config_not_found_error">Konfigurationsfilen \"%s\" blev ikke fundet</string>
<string name="config_rename_error">Kan ikke omdøbe konfigurationsfilen \"%s\"</string>
<string name="config_save_error">Kan ikke gemme konfigurationen for \"%1$s\": %2$s</string>
<string name="config_save_success">Konfiguration for \"%s\" blev gemt</string>
<string name="create_activity_title">Opret WireGuard tunnel</string>
<string name="create_empty">Opret fra ny</string>
<string name="create_from_file">Importér fra fil eller arkiv</string>
@@ -88,6 +90,7 @@
<string name="dns_search_domains">DNS-søgedomæner</string>
<string name="edit">Redigér</string>
<string name="endpoint">Slutpunkt</string>
<string name="error_prepare">Fejl ved forberedelse af tunnel: %s</string>
<string name="error_up">Fejl under aktivering af tunnel: %s</string>
<string name="exclude_private_ips">Eksludér private IP-adresser</string>
<string name="generate_new_private_key">Generér ny privat nøgle</string>
@@ -152,6 +155,7 @@
<string name="tunnel_create_success">Tunnelen blev succesfuldt oprettet \"%s\"</string>
<string name="tunnel_error_already_exists">Tunnel \"%s\" eksisterer allerede</string>
<string name="tunnel_error_invalid_name">Ugyldigt navn</string>
<string name="tunnel_list_placeholder">Tilføj en tunnel ved hjælp af knappen nedenfor</string>
<string name="tunnel_name">Tunnel Navn</string>
<string name="tunnel_dns_failure">Kunne ikke opslå DNS adresse: \"%s\"</string>
<string name="tunnel_rename_error">Kan ikke omdøbe tunnel: %s</string>
@@ -107,6 +107,9 @@
<string name="tv_select_a_storage_drive">Selecciones un dispositivo de almacenamiento</string>
<string name="tv_no_file_picker">Por favor, instale una herramienta de gestión de archivos para navegar por archivos</string>
<string name="tv_add_tunnel_get_started">Agregue un túnel para empezar</string>
<string name="donate_title">♥ Donar al Proyecto WireGuard</string>
<string name="donate_summary">Todas las contribuciones ayudan</string>
<string name="donate_google_play_disappointment">¡Gracias por apoyar el proyecto WireGuard!\n\nLamentablemente, debido a las políticas de Google, no estamos autorizados a enlazar a la parte de la página web del proyecto donde puedes hacer una donación. ¡Esperemos que puedas averiguar esto!\n\nGracias de nuevo por tu contribución.</string>
<string name="disable_config_export_title">Inhabilitar la exportación de configuración</string>
<string name="disable_config_export_description">Desactivar la exportación de configuración hace que las claves privadas sean menos accesibles</string>
<string name="dns_servers">Servidores DNS</string>
@@ -116,6 +119,7 @@
<string name="error_down">Error al bajar el túnel: %s</string>
<string name="error_fetching_apps">Error al obtener la lista de aplicaciones: %s</string>
<string name="error_root">Por favor, obtén acceso root y vuelve a intentarlo</string>
<string name="error_prepare">Error al preparar el túnel: %s</string>
<string name="error_up">Error al abrir el túnel: %s</string>
<string name="exclude_private_ips">Excluir direcciones privadas</string>
<string name="generate_new_private_key">Generar nueva clave privada</string>
@@ -135,6 +139,8 @@
<string name="key_length_explanation_base64">Las claves base64 de WireGuard deben tener 44 caracteres (32 bytes)</string>
<string name="key_length_explanation_binary">: Las claves WireGuard deben tener 32 bytes</string>
<string name="key_length_explanation_hex">: Las claves hexadecimales de Wirex deben tener 64 caracteres (32 bytes)</string>
<string name="latest_handshake">Última comunicación</string>
<string name="latest_handshake_ago">hace %s</string>
<string name="listen_port">Puerto de escucha</string>
<string name="log_export_error">No se pudo exportar el registro: %s</string>
<string name="log_export_subject">Archivo de registro WireGuard Android</string>
@@ -213,6 +219,7 @@
<string name="tunnel_create_success">Túnel creado con éxito “%s”</string>
<string name="tunnel_error_already_exists">Túnel “%s” ya existe</string>
<string name="tunnel_error_invalid_name">Nombre inválido</string>
<string name="tunnel_list_placeholder">Añadir un túnel usando el botón azul de abajo</string>
<string name="tunnel_name">Nombre del túnel</string>
<string name="tunnel_on_error">No se puede activar el túnel (wgTurnOn devolvió %d)</string>
<string name="tunnel_dns_failure">No se puede resolver el nombre de host DNS: “%s”</string>
+18 -10
View File
@@ -25,7 +25,7 @@
<item quantity="other">%d välistatud rakendust</item>
</plurals>
<plurals name="set_included_applications">
<item quantity="one">%d kaasatud rakendust</item>
<item quantity="one">%d kaasatud rakendus</item>
<item quantity="other">%d kaasatud rakendust</item>
</plurals>
<plurals name="n_excluded_applications">
@@ -79,8 +79,8 @@
<string name="bad_config_reason_unknown_section">Tundmatu sektsioon</string>
<string name="bad_config_reason_value_out_of_range">Väärtus lubatud vahemikust väljas</string>
<string name="bad_extension_error">Faililaiend peab olema .conf või .zip</string>
<string name="error_no_qr_found">QR koodi ei leitud pildilt</string>
<string name="error_qr_checksum">QR koodi kontrollsumma verifitseerimine ebaõnnestus</string>
<string name="error_no_qr_found">QR-koodi ei leitud pildilt</string>
<string name="error_qr_checksum">QR-koodi kontrollsumma verifitseerimine ebaõnnestus</string>
<string name="cancel">Tühista</string>
<string name="config_delete_error">Seadistusfaili %s kustutamine ebaõnnestus</string>
<string name="config_exists_error">\"%s\" seadistus on juba olemas</string>
@@ -90,8 +90,8 @@
<string name="config_save_error">\"%1$s\" seadistuse salvestamine ebaõnnestus: %2$s</string>
<string name="config_save_success">\"%s\" seadistus salvestatud</string>
<string name="create_activity_title">Loo WireGuard tunnel</string>
<string name="create_bin_dir_error">Ei saa luua lokaalset binary kataloogi</string>
<string name="create_downloads_file_error">Ei saa luua faili allalaadimiste kataloogis</string>
<string name="create_bin_dir_error">Lokaalse programmfaili kataloogi tekitamine ebaõnnestus</string>
<string name="create_downloads_file_error">Faili loomine allalaadimiste kataloogis ebaõnnestus</string>
<string name="create_empty">Sisesta käsitsi</string>
<string name="create_from_file">Impordi failist või arhiivist</string>
<string name="create_from_qr_code">Skaneeri QR-koodist</string>
@@ -107,15 +107,20 @@
<string name="tv_select_a_storage_drive">Vali salvestusseade</string>
<string name="tv_no_file_picker">Failide vaatamiseks paigalda failide haldusvahend</string>
<string name="tv_add_tunnel_get_started">Alustamiseks lisa uus tunnel</string>
<string name="donate_title">♥ Anneta WireGuard\'i projektile</string>
<string name="donate_summary">Iga panus aitab</string>
<string name="donate_google_play_disappointment">Aitäh, et toetad WireGuard\'i projekti!\n\nKahjuks ei saa me Google\'i eeskirjade tõttu linkida projekti veebilehele, kus saab annetusi teha. Loodetavasti leiad selle ise!\n\n
Aitäh veelkord sinu panuse eest.</string>
<string name="disable_config_export_title">Keela seadistuste eksportimine</string>
<string name="disable_config_export_description">Seadistuste eksportimise keelamine teeb privaatvõtmetele ligipääsu keerulisemaks</string>
<string name="dns_servers">DNS serverid</string>
<string name="dns_servers">DNS-serverid</string>
<string name="dns_search_domains">DNS-i otsingudomeenid</string>
<string name="edit">Muuda</string>
<string name="endpoint">Lõpp-punkt</string>
<string name="error_down">Viga tunneli väljalülitamisel: %s</string>
<string name="error_fetching_apps">Viga rakenduste nimekirja pärimisel: %s</string>
<string name="error_root">Hangi juurkasutaja õigused ja proovi uuesti</string>
<string name="error_prepare">Viga tunneli ettevalmistamisel: %s</string>
<string name="error_up">Viga tunneli sisselülitamisel: %s</string>
<string name="exclude_private_ips">Keela privaatsed IP\'d</string>
<string name="generate_new_private_key">Tekita uus privaatvõti</string>
@@ -135,6 +140,8 @@
<string name="key_length_explanation_base64">: WireGuard base64 võti peab olema 44 sümbolit (32 baiti) pikk</string>
<string name="key_length_explanation_binary">: WireGuard võtmed peavad olema 32 baiti</string>
<string name="key_length_explanation_hex">: WireGuard hex võti peab olema 64 sümbolit (32 baiti) pikk</string>
<string name="latest_handshake">Viimane kätlus</string>
<string name="latest_handshake_ago">%s tagasi</string>
<string name="listen_port">Kuulamisport</string>
<string name="log_export_error">Logifaili eksportimine ebaõnnestus: %s</string>
<string name="log_export_subject">WireGuard Android logifail</string>
@@ -164,7 +171,7 @@
<string name="no_configs_error">Seadistusi ei leitud</string>
<string name="no_tunnels_error">Tunneleid ei ole</string>
<string name="parse_error_generic">string</string>
<string name="parse_error_inet_address">IP aadress</string>
<string name="parse_error_inet_address">IP-aadress</string>
<string name="parse_error_inet_endpoint">lõpp-punkt</string>
<string name="parse_error_inet_network">IP võrk</string>
<string name="parse_error_integer">number</string>
@@ -184,9 +191,9 @@
<string name="save">Salvesta</string>
<string name="select_all">Vali kõik</string>
<string name="settings">Seaded</string>
<string name="shell_exit_status_read_error">Kest, shell, ei saa lugeda väljumise olekut</string>
<string name="shell_marker_count_error">Kest, shell, ootas 4 markerit, saadi %d</string>
<string name="shell_start_error">Kest, shell, ebaõnnestus käivitamisel: %d</string>
<string name="shell_exit_status_read_error">Kest ei saa lugeda väljundstaatust</string>
<string name="shell_marker_count_error">Kest ootas 4 markerit, saadi %d</string>
<string name="shell_start_error">Kesta käivitumine ebaõnnestus: %d</string>
<string name="success_application_will_restart">Tegevus õnnestus. Rakendus taaskäivitub…</string>
<string name="toggle_all">Vaheta kõik</string>
<string name="toggle_error">WireGuard tunneli lülitamine ebaõnnestus: %s</string>
@@ -213,6 +220,7 @@
<string name="tunnel_create_success">Tunnel \"%s\" lisatud</string>
<string name="tunnel_error_already_exists">Tunnel \"%s\" on juba olemas</string>
<string name="tunnel_error_invalid_name">Sobimatu nimi</string>
<string name="tunnel_list_placeholder">Lisa tunnel alloleva nupu abil</string>
<string name="tunnel_name">Tunneli nimi</string>
<string name="tunnel_on_error">Tunneli sisselülitamine ebaõnnestus (wgTurnOn tagastas %d)</string>
<string name="tunnel_dns_failure">Ei saa lahendada DNS hostinime: \"%s\"</string>
+9 -1
View File
@@ -66,6 +66,8 @@
<string name="bad_config_reason_unknown_section">未知のセクション</string>
<string name="bad_config_reason_value_out_of_range">範囲外の値</string>
<string name="bad_extension_error">ファイルの拡張子は .conf か .zip です</string>
<string name="error_no_qr_found">QRコードが見つかりません</string>
<string name="error_qr_checksum">QRコードのチェックサムの確認に失敗しました</string>
<string name="cancel">キャンセル</string>
<string name="config_delete_error">設定ファイル %s を削除できません</string>
<string name="config_exists_error">\"%s\" の定義はすでに存在します</string>
@@ -91,7 +93,9 @@
<string name="tv_delete">削除するトンネルを選択</string>
<string name="tv_select_a_storage_drive">ストレージを選択</string>
<string name="tv_no_file_picker">ファイルを参照するにはファイル管理アプリをインストールしてください</string>
<string name="tv_add_tunnel_get_started">Add a tunnel to get started</string>
<string name="tv_add_tunnel_get_started">トンネルを追加して開始する</string>
<string name="donate_title">♥ WireGuard プロジェクトに寄付する</string>
<string name="donate_summary">すべての貢献が役立ちます</string>
<string name="disable_config_export_title">設定のエクスポートを無効にする</string>
<string name="disable_config_export_description">設定のエクスポートを無効にすると、秘密鍵にアクセスされにくくなります</string>
<string name="dns_servers">DNS サーバ</string>
@@ -101,6 +105,7 @@
<string name="error_down">トンネル停止時エラー: %s</string>
<string name="error_fetching_apps">アプリ一覧取得エラー: %s</string>
<string name="error_root">root 権限を取得して再試行してください</string>
<string name="error_prepare">トンネル準備中エラー: %s</string>
<string name="error_up">トンネル起動時エラー: %s</string>
<string name="exclude_private_ips">プライベート IP アドレスを除外</string>
<string name="generate_new_private_key">新しい秘密鍵を生成する</string>
@@ -120,6 +125,8 @@
<string name="key_length_explanation_base64">: WireGuard base64 鍵は44文字 (32バイト) でなければなりません</string>
<string name="key_length_explanation_binary">: WireGuard 鍵は32バイトでなければなりません</string>
<string name="key_length_explanation_hex">: WireGuard hex 鍵は64文字 (32バイト) でなければなりません</string>
<string name="latest_handshake">直近のハンドシェイク</string>
<string name="latest_handshake_ago">%s 前</string>
<string name="listen_port">Listen ポート</string>
<string name="log_export_error">ログをエクスポートできません: %s</string>
<string name="log_export_subject">WireGuard Android ログファイル</string>
@@ -198,6 +205,7 @@
<string name="tunnel_create_success">トンネル \"%s\" を作成しました</string>
<string name="tunnel_error_already_exists">トンネル “%s” はすでに存在します</string>
<string name="tunnel_error_invalid_name">不正な名前</string>
<string name="tunnel_list_placeholder">下のボタンを使用してトンネルを追加</string>
<string name="tunnel_name">トンネル名</string>
<string name="tunnel_on_error">トンネルを有効にできません (wgTurnOn が %d を返却)</string>
<string name="tunnel_dns_failure">DNSホスト名を解決できませんでした: “%s”</string>
+11 -1
View File
@@ -21,7 +21,7 @@
<item quantity="other">Importerte %d tunneler</item>
</plurals>
<plurals name="set_excluded_applications">
<item quantity="one">%d ekskludert program</item>
<item quantity="one">%d ekskludert app</item>
<item quantity="other">%d ekskluderte apper</item>
</plurals>
<plurals name="set_included_applications">
@@ -79,6 +79,8 @@
<string name="bad_config_reason_unknown_section">Ukjent seksjon</string>
<string name="bad_config_reason_value_out_of_range">Verdien er utenfor gyldig område</string>
<string name="bad_extension_error">Filen må være .conf eller .zip</string>
<string name="error_no_qr_found">Ingen QR-kode funnet i bildet</string>
<string name="error_qr_checksum">Feil ved sjekksumverifisering av QR-kode</string>
<string name="cancel">Avbryt</string>
<string name="config_delete_error">Kan ikke slette konfigurasjonsfilen %s</string>
<string name="config_exists_error">Konfigurasjon for «%s» finnes allerede</string>
@@ -105,14 +107,19 @@
<string name="tv_select_a_storage_drive">Velg en lagringsenhet</string>
<string name="tv_no_file_picker">Vennligst installer et filhåndteringsverktøy for å bla i filer</string>
<string name="tv_add_tunnel_get_started">Opprett en ny tunnel for å komme i gang</string>
<string name="donate_title">♥ Donér til WireGuard-prosjektet</string>
<string name="donate_summary">Hvert bidrag hjelper</string>
<string name="donate_google_play_disappointment">Takk for at du støtter WireGuard-prosjektet!\n\nPå grunn av Googles retningslinjer, kan vi dessverre ikke linke til den delen av prosjektets nettside der du kan donere. Forhåpentligvis klarer du å finne denne selv!\n\nVi takker igjen for ditt bidrag.</string>
<string name="disable_config_export_title">Deaktiver eksport av konfigurasjon</string>
<string name="disable_config_export_description">Deaktivering av konfigurasjonseksport gjør private nøkler mindre tilgjengelig</string>
<string name="dns_servers">DNS tjenere</string>
<string name="dns_search_domains">Søk gjennom domener</string>
<string name="edit">Rediger</string>
<string name="endpoint">Endepunkt</string>
<string name="error_down">Feil når tunnel skulle tas ned: %s</string>
<string name="error_fetching_apps">Feil ved henting av applikasjonsliste: %s</string>
<string name="error_root">Vennligst få root-tilgang og prøv igjen</string>
<string name="error_prepare">Feil ved klargjøring av tunnel: %s</string>
<string name="error_up">Feil når tunnel skulle tas opp: %s</string>
<string name="exclude_private_ips">Utelukk private IP-adresser</string>
<string name="generate_new_private_key">Lag ny privat nøkkel</string>
@@ -210,13 +217,16 @@
<string name="tunnel_create_success">Opprettet tunnelen «%s»</string>
<string name="tunnel_error_already_exists">Tunnel «%s» finnes allerede</string>
<string name="tunnel_error_invalid_name">Ugyldig navn</string>
<string name="tunnel_list_placeholder">Legg til en tunnel ved å bruke knappen under</string>
<string name="tunnel_name">Tunnelnavn</string>
<string name="tunnel_on_error">Kan ikke slå på tunnel (wgTurnOn returnerte %d)</string>
<string name="tunnel_dns_failure">Kan ikke slå opp DNS-vertsnavn: “%s\"</string>
<string name="tunnel_rename_error">Kan ikke endre navn på tunnel: %s</string>
<string name="tunnel_rename_success">Endret navn på tunnelen til «%s»</string>
<string name="type_name_go_userspace">Bruk userspace</string>
<string name="type_name_kernel_module">Kjernemodul</string>
<string name="unknown_error">Ukjent feil</string>
<string name="version_summary">%1$s backend %2$s</string>
<string name="version_summary_checking">Sjekker %s backend versjon</string>
<string name="version_summary_unknown">Ukjent %s versjon</string>
<string name="version_title">WireGuard for Android v%s</string>
+7
View File
@@ -133,6 +133,9 @@
<string name="tv_select_a_storage_drive">Выберите накопитель</string>
<string name="tv_no_file_picker">Пожалуйста, установите утилиту управления файлами для их просмотра</string>
<string name="tv_add_tunnel_get_started">Добавьте туннель, чтобы начать</string>
<string name="donate_title">♥ Пожертвовать проекту WireGuard</string>
<string name="donate_summary">Каждое пожертвование помогает</string>
<string name="donate_google_play_disappointment">Спасибо за поддержку проекта WireGuard!\n\nК сожалению, из-за политики Google, нельзя размещать ссылку на тот раздел сайта проекта, где можно сделать пожертвование. Надеемся, вы сможете разобраться самостоятельно!\n\nЕщё раз спасибо за ваш вклад.</string>
<string name="disable_config_export_title">Отключить экспорт конфигурации</string>
<string name="disable_config_export_description">Отключение экспорта конфигурации делает приватные ключи менее доступными</string>
<string name="dns_servers">DNS-серверы</string>
@@ -142,6 +145,7 @@
<string name="error_down">Ошибка при выходе из туннеля: %s</string>
<string name="error_fetching_apps">Ошибка при получении списка приложений: %s</string>
<string name="error_root">Пожалуйста, получите root-доступ и попробуйте снова</string>
<string name="error_prepare">Ошибка при подготовке туннеля: %s</string>
<string name="error_up">Ошибка при запуске туннеля: %s</string>
<string name="exclude_private_ips">Исключить частные IP-адреса</string>
<string name="generate_new_private_key">Сгенерировать новый приватный ключ</string>
@@ -161,6 +165,8 @@
<string name="key_length_explanation_base64">: ключи WireGuard base64 должны содержать 44 символа (32 байта)</string>
<string name="key_length_explanation_binary">: ключи WireGuard должны быть 32 байта</string>
<string name="key_length_explanation_hex">: HEX-ключи WireGuard должны содержать 64 символа (32 байта)</string>
<string name="latest_handshake">Последнее рукопожатие</string>
<string name="latest_handshake_ago">%s назад</string>
<string name="listen_port">Порт</string>
<string name="log_export_error">Не удалось экспортировать журнал: %s</string>
<string name="log_export_subject">Файл журнала WireGuard Android</string>
@@ -239,6 +245,7 @@
<string name="tunnel_create_success">Успешно создан туннель “%s”</string>
<string name="tunnel_error_already_exists">Туннель “%s” уже существует</string>
<string name="tunnel_error_invalid_name">Неправильное имя</string>
<string name="tunnel_list_placeholder">Добавьте туннель с помощью кнопки ниже</string>
<string name="tunnel_name">Название туннеля</string>
<string name="tunnel_on_error">Не удалось включить туннель (wgTurnOn вернул %d)</string>
<string name="tunnel_dns_failure">Не удалось определить DNS имя: “%s”</string>
@@ -133,6 +133,8 @@
<string name="tv_select_a_storage_drive">Виберіть диск зберігання</string>
<string name="tv_no_file_picker">Будь ласка, встановіть провідник для перегляду файлів</string>
<string name="tv_add_tunnel_get_started">Додайте тунель, щоб почати</string>
<string name="donate_title">♥️ Пожертвуйте на проект WireGuard</string>
<string name="donate_summary">Кожен внесок допомагає</string>
<string name="disable_config_export_title">Вимкнути експорт конфігурації</string>
<string name="disable_config_export_description">Вимкнення експорту налаштувань робить приватні ключі менш доступними</string>
<string name="dns_servers">DNS-сервери</string>
@@ -142,6 +144,7 @@
<string name="error_down">Помилка вимкнення тунелю: %s</string>
<string name="error_fetching_apps">Помилка при отриманні списку додатків: %s</string>
<string name="error_root">Будь ласка, отримайте root-доступ і спробуйте ще раз</string>
<string name="error_prepare">Помилка підготовки тунелю: %s</string>
<string name="error_up">Помилка при увімкненні тунелю: %s</string>
<string name="exclude_private_ips">Виключити приватні IP</string>
<string name="generate_new_private_key">Згенерувати новий приватний ключ</string>
@@ -161,6 +164,8 @@
<string name="key_length_explanation_base64">: Ключі WireGuard base64 повинні мати довжину 44 символи (32 байти)</string>
<string name="key_length_explanation_binary">: Ключі WireGuard повинні мати довжину 32 байти</string>
<string name="key_length_explanation_hex">: hex ключі WireGuard повинні мати довжину 64 символи (32 байти)</string>
<string name="latest_handshake">Останнє рукостискання</string>
<string name="latest_handshake_ago">%s тому</string>
<string name="listen_port">Порт</string>
<string name="log_export_error">Не вдалося експортувати журнал: %s</string>
<string name="log_export_subject">Файл журналу WireGuard</string>
@@ -239,6 +244,7 @@
<string name="tunnel_create_success">Тунель успішно створено “%s”</string>
<string name="tunnel_error_already_exists">Тунель \"%s\" вже існує</string>
<string name="tunnel_error_invalid_name">Неприпустиме ім\'я</string>
<string name="tunnel_list_placeholder">Додайте тунель, використовуючи кнопку нижче</string>
<string name="tunnel_name">Назва тунелю</string>
<string name="tunnel_on_error">Неможливо ввімкнути тунель (wgTurnon returned %d)</string>
<string name="tunnel_dns_failure">Не вдалося знайти DNS хост: “%s</string>
@@ -106,6 +106,7 @@
<string name="error_down">断开连接时出错:%s</string>
<string name="error_fetching_apps">获取应用列表时出错:%s</string>
<string name="error_root">请获取 root 权限并重试</string>
<string name="error_prepare">准备连接时出错:%s</string>
<string name="error_up">建立连接时出错:%s</string>
<string name="exclude_private_ips">排除局域网</string>
<string name="generate_new_private_key">生成新的私钥</string>
@@ -125,6 +126,8 @@
<string name="key_length_explanation_base64">:WireGuard 的 Base64 密钥长度必须为 44 个字符(32 字节)</string>
<string name="key_length_explanation_binary">:WireGuard 密钥大小必须为 32 字节</string>
<string name="key_length_explanation_hex">:WireGuard 的十六进制密钥长度必须为 64 个字符(32 字节)</string>
<string name="latest_handshake">上次握手时间</string>
<string name="latest_handshake_ago">%s之前</string>
<string name="listen_port">监听端口</string>
<string name="log_export_error">无法导出日志:%s</string>
<string name="log_export_subject">WireGuard 日志文件</string>
-1
View File
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="fab_margin">16dp</dimen>
<dimen name="extra_margin">12dp</dimen>
<dimen name="bottom_sheet_item_height">56dp</dimen>
<dimen name="normal_margin">8dp</dimen>
<dimen name="bottom_sheet_top_padding">8dp</dimen>
+9
View File
@@ -140,6 +140,8 @@
<string name="key_length_explanation_base64">: WireGuard base64 keys must be 44 characters (32 bytes)</string>
<string name="key_length_explanation_binary">: WireGuard keys must be 32 bytes</string>
<string name="key_length_explanation_hex">: WireGuard hex keys must be 64 characters (32 bytes)</string>
<string name="latest_handshake">Latest handshake</string>
<string name="latest_handshake_ago">%s ago</string>
<string name="listen_port">Listen port</string>
<string name="log_export_error">Unable to export log: %s</string>
<string name="log_export_subject">WireGuard Android Log File</string>
@@ -227,6 +229,13 @@
<string name="type_name_go_userspace">Go userspace</string>
<string name="type_name_kernel_module">Kernel module</string>
<string name="unknown_error">Unknown error</string>
<string name="updater_avalable">An application update is available. Please update now.</string>
<string name="updater_action">Download &amp; Update</string>
<string name="updater_rechecking">Fetching update metadata…</string>
<string name="updater_download_progress">Downloading update: %1$s / %2$s (%3$.2f%%)</string>
<string name="updater_download_progress_nototal">Downloading update: %s</string>
<string name="updater_installing">Installing update…</string>
<string name="updater_failure">Update failure: %s. Will retry momentarily…</string>
<string name="version_summary">%1$s backend %2$s</string>
<string name="version_summary_checking">Checking %s backend version</string>
<string name="version_summary_unknown">Unknown %s version</string>
-6
View File
@@ -1,6 +0,0 @@
buildscript {
ext {
wireguardVersionCode = 498
wireguardVersionName = '1.0.20230405'
}
}