mirror of
https://github.com/amnezia-vpn/amneziawg-android.git
synced 2026-07-03 14:05:13 +02:00
Compare commits
87 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 04e503c9db | |||
| 434534ac59 | |||
| 0f77af85d3 | |||
| 58789d6372 | |||
| adefd16f7e | |||
| e4a49a5356 | |||
| 353679801e | |||
| 58ec110360 | |||
| a7ce5ad42a | |||
| 52c0d1ea29 | |||
| a5b4b9c428 | |||
| e744ce4569 | |||
| adfd8e247b | |||
| e9243c403a | |||
| fbcbfbd311 | |||
| dc1d294a82 | |||
| 2abd426cdc | |||
| c536f5cc7f | |||
| 37a892a2da | |||
| f2d166a6a4 | |||
| d883802178 | |||
| 8d990be204 | |||
| 5fa2e6164f | |||
| 9972d9607e | |||
| 6a72194106 | |||
| 254e6164d7 | |||
| 675386241b | |||
| 8671acc1a6 | |||
| fb08bb6bd4 | |||
| 70f8e5ab3a | |||
| e53a8d76f9 | |||
| e0b4551bca | |||
| 7b9757bf3b | |||
| 4156b83b84 | |||
| f829b8abe0 | |||
| 838e0bd6b0 | |||
| c3f6906309 | |||
| 5cafd1ebe4 | |||
| 51991324a9 | |||
| 33a8a51962 | |||
| 40eaa54cf0 | |||
| a3bfa6f1ab | |||
| 7778ff63b5 | |||
| 79c5eb41c3 | |||
| 9e30c7d2e9 | |||
| d80129ba46 | |||
| 6907663f07 | |||
| dca96a52b4 | |||
| cf4fca8086 | |||
| 318f36a205 | |||
| 87aa73cc64 | |||
| 4e28d57aee | |||
| 6d67a3d5f5 | |||
| 1b5f4d6dfd | |||
| bed2f2e5d6 | |||
| 7d91666860 | |||
| 8800f861ed | |||
| 99336f6552 | |||
| 0b7b65296b | |||
| e2f931b4cb | |||
| d6ad7d11d0 | |||
| 914917036e | |||
| 6e9ca435ca | |||
| 0797e99559 | |||
| 0ba064ff48 | |||
| b4fe9efadb | |||
| b1b08ce716 | |||
| 20480992c4 | |||
| 9df90b259a | |||
| b94a412c09 | |||
| 0b0a553123 | |||
| 6d4d2f4ebf | |||
| e55358f685 | |||
| c2fdd0ca93 | |||
| 19044b0f53 | |||
| d4e77e6d57 | |||
| 6ba3e4f001 | |||
| 8152191277 | |||
| 8c6c909e98 | |||
| fdda87fd31 | |||
| 3907f6a4c1 | |||
| 652971d746 | |||
| 1bf8d7e4c4 | |||
| 3c834ac8e3 | |||
| 8669ae78e1 | |||
| 53f8c8dbc4 | |||
| 1328660b0b |
+1
-1
@@ -14,5 +14,5 @@ build/
|
||||
*.dex
|
||||
*.iml
|
||||
*.jks
|
||||
keystore.properties
|
||||
gradlew.bat
|
||||
maint/
|
||||
|
||||
Generated
+1
@@ -468,6 +468,7 @@
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
<option name="RIGHT_MARGIN" value="160" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
@@ -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"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -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
@@ -1,3 +1,7 @@
|
||||
wireguardVersionCode=509
|
||||
wireguardVersionName=1.0.20230707
|
||||
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
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
[versions]
|
||||
agp = "8.2.0-alpha10"
|
||||
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.1"
|
||||
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.9.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.7.0"
|
||||
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" }
|
||||
Vendored
BIN
Binary file not shown.
+2
-2
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
@@ -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")
|
||||
@@ -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"
|
||||
@@ -0,0 +1,128 @@
|
||||
@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 = 34
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
@@ -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,41 @@
|
||||
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<>();
|
||||
public record PeerStats(long rxBytes, long txBytes, long 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 +54,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 stats.get(peer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,7 +71,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 +81,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 +94,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) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,8 +46,8 @@ public class RootShell {
|
||||
final String packageName = context.getPackageName();
|
||||
if (packageName.contains("'"))
|
||||
throw new RuntimeException("Impossibly invalid package name contains a single quote");
|
||||
preamble = String.format("export CALLING_PACKAGE=%s PATH=\"%s:$PATH\" TMPDIR='%s'; magisk --sqlite \"UPDATE policies SET notification=0, logging=0 WHERE package_name='%s'\" >/dev/null 2>&1; id -u\n",
|
||||
packageName, localBinaryDir, localTemporaryDir, packageName);
|
||||
preamble = String.format("export CALLING_PACKAGE='%s' PATH=\"%s:$PATH\" TMPDIR='%s'; magisk --sqlite \"UPDATE policies SET notification=0, logging=0 WHERE uid=%d\" >/dev/null 2>&1; id -u\n",
|
||||
packageName, localBinaryDir, localTemporaryDir, android.os.Process.myUid());
|
||||
}
|
||||
|
||||
private static boolean isExecutableInPath(final String name) {
|
||||
|
||||
@@ -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}
|
||||
)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
@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 = 34
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
dataBinding = true
|
||||
viewBinding = true
|
||||
}
|
||||
namespace = pkg
|
||||
defaultConfig {
|
||||
applicationId = pkg
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
versionCode = providers.gradleProperty("wireguardVersionCode").get().toInt()
|
||||
versionName = providers.gradleProperty("wireguardVersionName").get()
|
||||
buildConfigField("int", "MIN_SDK_VERSION", minSdk.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"
|
||||
excludes += "META-INF/*.version"
|
||||
}
|
||||
}
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix = ".debug"
|
||||
versionNameSuffix = "-debug"
|
||||
}
|
||||
create("googleplay") {
|
||||
initWith(getByName("release"))
|
||||
matchingFallbacks += "release"
|
||||
}
|
||||
}
|
||||
androidResources {
|
||||
generateLocaleConfig = true
|
||||
}
|
||||
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,7 +1,6 @@
|
||||
-allowaccessmodification
|
||||
-dontpreverify
|
||||
-dontusemixedcaseclassnames
|
||||
-dontskipnonpubliclibraryclasses
|
||||
-dontobfuscate
|
||||
-verbose
|
||||
|
||||
-keepattributes *Annotation*
|
||||
|
||||
Vendored
-14
@@ -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
|
||||
@@ -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" />
|
||||
</manifest>
|
||||
@@ -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"
|
||||
@@ -44,10 +45,12 @@
|
||||
|
||||
<activity
|
||||
android:name=".activity.TunnelToggleActivity"
|
||||
android:theme="@style/NoBackgroundTheme"
|
||||
android:excludeFromRecents="true"/>
|
||||
android:excludeFromRecents="true"
|
||||
android:theme="@style/NoBackgroundTheme" />
|
||||
|
||||
<activity android:name=".activity.MainActivity" android:exported="true">
|
||||
<activity
|
||||
android:name=".activity.MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
@@ -61,8 +64,8 @@
|
||||
|
||||
<activity
|
||||
android:name=".activity.TvMainActivity"
|
||||
android:theme="@style/TvTheme"
|
||||
android:exported="true">
|
||||
android:exported="true"
|
||||
android:theme="@style/TvTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
@@ -86,8 +89,8 @@
|
||||
|
||||
<activity
|
||||
android:name=".activity.LogViewerActivity"
|
||||
android:label="@string/log_viewer_title"
|
||||
android:exported="false">
|
||||
android:exported="false"
|
||||
android:label="@string/log_viewer_title">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
</intent-filter>
|
||||
@@ -99,7 +102,9 @@
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true" />
|
||||
|
||||
<receiver android:name=".BootShutdownReceiver" android:exported="true">
|
||||
<receiver
|
||||
android:name=".BootShutdownReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.ACTION_SHUTDOWN" />
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
@@ -107,9 +112,17 @@
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".model.TunnelManager$IntentReceiver"
|
||||
android:permission="${applicationId}.permission.CONTROL_TUNNELS"
|
||||
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:exported="true"
|
||||
android:permission="${applicationId}.permission.CONTROL_TUNNELS">
|
||||
<intent-filter>
|
||||
<action android:name="com.wireguard.android.action.REFRESH_TUNNEL_STATES" />
|
||||
<action android:name="com.wireguard.android.action.SET_TUNNEL_UP" />
|
||||
@@ -119,9 +132,9 @@
|
||||
|
||||
<service
|
||||
android:name=".QuickTileService"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_tile"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||
android:exported="true">
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
@@ -130,6 +143,10 @@
|
||||
<meta-data
|
||||
android:name="android.service.quicksettings.ACTIVE_TILE"
|
||||
android:value="false" />
|
||||
|
||||
<meta-data
|
||||
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
|
||||
android:value="true" />
|
||||
</service>
|
||||
|
||||
<meta-data
|
||||
|
||||
@@ -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)")
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
package com.wireguard.android
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
@@ -49,32 +50,36 @@ class QuickTileService : TileService() {
|
||||
}
|
||||
|
||||
override fun onClick() {
|
||||
if (tunnel != null) {
|
||||
unlockAndRun {
|
||||
val tile = qsTile
|
||||
if (tile != null) {
|
||||
tile.icon = if (tile.icon == iconOn) iconOff else iconOn
|
||||
tile.updateTile()
|
||||
when (val tunnel = tunnel) {
|
||||
null -> {
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
startActivityAndCollapse(PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE))
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
startActivityAndCollapse(intent)
|
||||
}
|
||||
applicationScope.launch {
|
||||
try {
|
||||
tunnel!!.setStateAsync(Tunnel.State.TOGGLE)
|
||||
updateTile()
|
||||
} catch (_: Throwable) {
|
||||
val toggleIntent = Intent(this@QuickTileService, TunnelToggleActivity::class.java)
|
||||
toggleIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(toggleIntent)
|
||||
}
|
||||
else -> {
|
||||
unlockAndRun {
|
||||
applicationScope.launch {
|
||||
try {
|
||||
tunnel.setStateAsync(Tunnel.State.TOGGLE)
|
||||
updateTile()
|
||||
} catch (_: Throwable) {
|
||||
val toggleIntent = Intent(this@QuickTileService, TunnelToggleActivity::class.java)
|
||||
toggleIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(toggleIntent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivityAndCollapse(intent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
isAdded = true
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
iconOn = Icon.createWithResource(this, R.drawable.ic_tile)
|
||||
iconOff = iconOn
|
||||
@@ -84,55 +89,64 @@ class QuickTileService : TileService() {
|
||||
icon.setAnimationEnabled(false) /* Unfortunately we can't have animations, since Icons are marshaled. */
|
||||
icon.setSlashed(false)
|
||||
var b = Bitmap.createBitmap(icon.intrinsicWidth, icon.intrinsicHeight, Bitmap.Config.ARGB_8888)
|
||||
?: return
|
||||
var c = Canvas(b)
|
||||
icon.setBounds(0, 0, c.width, c.height)
|
||||
icon.draw(c)
|
||||
iconOn = Icon.createWithBitmap(b)
|
||||
icon.setSlashed(true)
|
||||
b = Bitmap.createBitmap(icon.intrinsicWidth, icon.intrinsicHeight, Bitmap.Config.ARGB_8888)
|
||||
?: return
|
||||
c = Canvas(b)
|
||||
icon.setBounds(0, 0, c.width, c.height)
|
||||
icon.draw(c)
|
||||
iconOff = Icon.createWithBitmap(b)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
isAdded = false
|
||||
}
|
||||
|
||||
override fun onStartListening() {
|
||||
Application.getTunnelManager().addOnPropertyChangedCallback(onTunnelChangedCallback)
|
||||
if (tunnel != null) tunnel!!.addOnPropertyChangedCallback(onStateChangedCallback)
|
||||
tunnel?.addOnPropertyChangedCallback(onStateChangedCallback)
|
||||
updateTile()
|
||||
}
|
||||
|
||||
override fun onStopListening() {
|
||||
if (tunnel != null) tunnel!!.removeOnPropertyChangedCallback(onStateChangedCallback)
|
||||
tunnel?.removeOnPropertyChangedCallback(onStateChangedCallback)
|
||||
Application.getTunnelManager().removeOnPropertyChangedCallback(onTunnelChangedCallback)
|
||||
}
|
||||
|
||||
override fun onTileAdded() {
|
||||
isAdded = true
|
||||
}
|
||||
|
||||
override fun onTileRemoved() {
|
||||
isAdded = false
|
||||
}
|
||||
|
||||
private fun updateTile() {
|
||||
// Update the tunnel.
|
||||
val newTunnel = Application.getTunnelManager().lastUsedTunnel
|
||||
if (newTunnel != tunnel) {
|
||||
if (tunnel != null) tunnel!!.removeOnPropertyChangedCallback(onStateChangedCallback)
|
||||
tunnel?.removeOnPropertyChangedCallback(onStateChangedCallback)
|
||||
tunnel = newTunnel
|
||||
if (tunnel != null) tunnel!!.addOnPropertyChangedCallback(onStateChangedCallback)
|
||||
tunnel?.addOnPropertyChangedCallback(onStateChangedCallback)
|
||||
}
|
||||
// Update the tile contents.
|
||||
val label: String
|
||||
val state: Int
|
||||
val tile = qsTile
|
||||
if (tunnel != null) {
|
||||
label = tunnel!!.name
|
||||
state = if (tunnel!!.state == Tunnel.State.UP) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE
|
||||
} else {
|
||||
label = getString(R.string.app_name)
|
||||
state = Tile.STATE_INACTIVE
|
||||
}
|
||||
if (tile == null) return
|
||||
tile.label = label
|
||||
if (tile.state != state) {
|
||||
tile.icon = if (state == Tile.STATE_ACTIVE) iconOn else iconOff
|
||||
tile.state = state
|
||||
val tile = qsTile ?: return
|
||||
|
||||
when (val tunnel = tunnel) {
|
||||
null -> {
|
||||
tile.label = getString(R.string.app_name)
|
||||
tile.state = Tile.STATE_INACTIVE
|
||||
tile.icon = iconOff
|
||||
}
|
||||
else -> {
|
||||
tile.label = tunnel.name
|
||||
tile.state = if (tunnel.state == Tunnel.State.UP) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE
|
||||
tile.icon = if (tunnel.state == Tunnel.State.UP) iconOn else iconOff
|
||||
}
|
||||
}
|
||||
tile.updateTile()
|
||||
}
|
||||
@@ -143,19 +157,23 @@ class QuickTileService : TileService() {
|
||||
sender.removeOnPropertyChangedCallback(this)
|
||||
return
|
||||
}
|
||||
if (propertyId != 0 && propertyId != BR.state) return
|
||||
if (propertyId != 0 && propertyId != BR.state)
|
||||
return
|
||||
updateTile()
|
||||
}
|
||||
}
|
||||
|
||||
private inner class OnTunnelChangedCallback : OnPropertyChangedCallback() {
|
||||
override fun onPropertyChanged(sender: Observable, propertyId: Int) {
|
||||
if (propertyId != 0 && propertyId != BR.lastUsedTunnel) return
|
||||
if (propertyId != 0 && propertyId != BR.lastUsedTunnel)
|
||||
return
|
||||
updateTile()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "WireGuard/QuickTileService"
|
||||
var isAdded: Boolean = false
|
||||
private set
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,8 @@ abstract class BaseActivity : AppCompatActivity() {
|
||||
protected abstract fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?, newTunnel: ObservableTunnel?): Boolean
|
||||
|
||||
fun removeOnSelectedTunnelChangedListener(
|
||||
listener: OnSelectedTunnelChangedListener) {
|
||||
listener: OnSelectedTunnelChangedListener
|
||||
) {
|
||||
selectionChangeRegistry.remove(listener)
|
||||
}
|
||||
|
||||
@@ -77,17 +78,17 @@ abstract class BaseActivity : AppCompatActivity() {
|
||||
|
||||
private class SelectionChangeNotifier : NotifierCallback<OnSelectedTunnelChangedListener, ObservableTunnel, ObservableTunnel>() {
|
||||
override fun onNotifyCallback(
|
||||
listener: OnSelectedTunnelChangedListener,
|
||||
oldTunnel: ObservableTunnel?,
|
||||
ignored: Int,
|
||||
newTunnel: ObservableTunnel?
|
||||
listener: OnSelectedTunnelChangedListener,
|
||||
oldTunnel: ObservableTunnel?,
|
||||
ignored: Int,
|
||||
newTunnel: ObservableTunnel?
|
||||
) {
|
||||
listener.onSelectedTunnelChanged(oldTunnel, newTunnel)
|
||||
}
|
||||
}
|
||||
|
||||
private class SelectionChangeRegistry :
|
||||
CallbackRegistry<OnSelectedTunnelChangedListener, ObservableTunnel, ObservableTunnel>(SelectionChangeNotifier())
|
||||
CallbackRegistry<OnSelectedTunnelChangedListener, ObservableTunnel, ObservableTunnel>(SelectionChangeNotifier())
|
||||
|
||||
companion object {
|
||||
private const val KEY_SELECTED_TUNNEL = "selected_tunnel"
|
||||
|
||||
@@ -112,19 +112,21 @@ class LogViewerActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
binding.shareFab.setOnClickListener {
|
||||
revokeLastUri()
|
||||
val key = KeyPair().privateKey.toHex()
|
||||
LOGS[key] = rawLogBytes()
|
||||
lastUri = Uri.parse("content://${BuildConfig.APPLICATION_ID}.exported-log/$key")
|
||||
val shareIntent = ShareCompat.IntentBuilder(this)
|
||||
lifecycleScope.launch {
|
||||
revokeLastUri()
|
||||
val key = KeyPair().privateKey.toHex()
|
||||
LOGS[key] = rawLogBytes()
|
||||
lastUri = Uri.parse("content://${BuildConfig.APPLICATION_ID}.exported-log/$key")
|
||||
val shareIntent = ShareCompat.IntentBuilder(this@LogViewerActivity)
|
||||
.setType("text/plain")
|
||||
.setSubject(getString(R.string.log_export_subject))
|
||||
.setStream(lastUri)
|
||||
.setChooserTitle(R.string.log_export_title)
|
||||
.createChooserIntent()
|
||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
grantUriPermission("android", lastUri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
revokeLastActivityResultLauncher.launch(shareIntent)
|
||||
grantUriPermission("android", lastUri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
revokeLastActivityResultLauncher.launch(shareIntent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,26 +142,28 @@ class LogViewerActivity : AppCompatActivity() {
|
||||
finish()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.save_log -> {
|
||||
saveButton?.isEnabled = false
|
||||
lifecycleScope.launch { saveLog() }
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
private val downloadsFileSaver = DownloadsFileSaver(this)
|
||||
|
||||
private fun rawLogBytes() : ByteArray {
|
||||
private suspend fun rawLogBytes(): ByteArray {
|
||||
val builder = StringBuilder()
|
||||
for (i in 0 until rawLogLines.size()) {
|
||||
builder.append(rawLogLines[i])
|
||||
builder.append('\n')
|
||||
withContext(Dispatchers.IO) {
|
||||
for (i in 0 until rawLogLines.size()) {
|
||||
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() {
|
||||
@@ -177,12 +181,14 @@ class LogViewerActivity : AppCompatActivity() {
|
||||
saveButton?.isEnabled = true
|
||||
if (outputFile == null)
|
||||
return
|
||||
Snackbar.make(findViewById(android.R.id.content),
|
||||
if (exception == null) getString(R.string.log_export_success, outputFile?.fileName)
|
||||
else getString(R.string.log_export_error, ErrorMessages[exception]),
|
||||
if (exception == null) Snackbar.LENGTH_SHORT else Snackbar.LENGTH_LONG)
|
||||
.setAnchorView(binding.shareFab)
|
||||
.show()
|
||||
Snackbar.make(
|
||||
findViewById(android.R.id.content),
|
||||
if (exception == null) getString(R.string.log_export_success, outputFile?.fileName)
|
||||
else getString(R.string.log_export_error, ErrorMessages[exception]),
|
||||
if (exception == null) Snackbar.LENGTH_SHORT else Snackbar.LENGTH_LONG
|
||||
)
|
||||
.setAnchorView(binding.shareFab)
|
||||
.show()
|
||||
}
|
||||
|
||||
private suspend fun streamingLog() = withContext(Dispatchers.IO) {
|
||||
@@ -203,7 +209,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) {
|
||||
@@ -285,7 +291,8 @@ class LogViewerActivity : AppCompatActivity() {
|
||||
*
|
||||
* <pre>05-26 11:02:36.886 5689 5689 D AndroidRuntime: CheckJNI is OFF.</pre>
|
||||
*/
|
||||
private val THREADTIME_LINE: Pattern = Pattern.compile("^(\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}.\\d{3})(?:\\s+[0-9A-Za-z]+)?\\s+(\\d+)\\s+(\\d+)\\s+([A-Z])\\s+(.+?)\\s*: (.*)$")
|
||||
private val THREADTIME_LINE: Pattern =
|
||||
Pattern.compile("^(\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}.\\d{3})(?:\\s+[0-9A-Za-z]+)?\\s+(\\d+)\\s+(\\d+)\\s+([A-Z])\\s+(.+?)\\s*: (.*)$")
|
||||
private val LOGS: MutableMap<String, ByteArray> = ConcurrentHashMap()
|
||||
private const val TAG = "WireGuard/LogViewerActivity"
|
||||
}
|
||||
@@ -308,7 +315,7 @@ class LogViewerActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.log_viewer_entry, parent, false)
|
||||
.inflate(R.layout.log_viewer_entry, parent, false)
|
||||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
@@ -319,8 +326,10 @@ class LogViewerActivity : AppCompatActivity() {
|
||||
else
|
||||
SpannableString("${line.tag}: ${line.msg}").apply {
|
||||
setSpan(StyleSpan(BOLD), 0, "${line.tag}:".length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
setSpan(ForegroundColorSpan(levelToColor(line.level)),
|
||||
0, "${line.tag}:".length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
setSpan(
|
||||
ForegroundColorSpan(levelToColor(line.level)),
|
||||
0, "${line.tag}:".length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
holder.layout.apply {
|
||||
findViewById<MaterialTextView>(R.id.log_date).text = line.time.toString()
|
||||
@@ -342,11 +351,11 @@ class LogViewerActivity : AppCompatActivity() {
|
||||
override fun insert(uri: Uri, values: ContentValues?): Uri? = null
|
||||
|
||||
override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? =
|
||||
logForUri(uri)?.let {
|
||||
val m = MatrixCursor(arrayOf(android.provider.OpenableColumns.DISPLAY_NAME, android.provider.OpenableColumns.SIZE), 1)
|
||||
m.addRow(arrayOf("wireguard-log.txt", it.size.toLong()))
|
||||
m
|
||||
}
|
||||
logForUri(uri)?.let {
|
||||
val m = MatrixCursor(arrayOf(android.provider.OpenableColumns.DISPLAY_NAME, android.provider.OpenableColumns.SIZE), 1)
|
||||
m.addRow(arrayOf("wireguard-log.txt", it.size.toLong()))
|
||||
m
|
||||
}
|
||||
|
||||
override fun onCreate(): Boolean = true
|
||||
|
||||
@@ -356,7 +365,8 @@ class LogViewerActivity : AppCompatActivity() {
|
||||
|
||||
override fun getType(uri: Uri): String? = logForUri(uri)?.let { "text/plain" }
|
||||
|
||||
override fun getStreamTypes(uri: Uri, mimeTypeFilter: String): Array<String>? = getType(uri)?.let { if (compareMimeTypes(it, mimeTypeFilter)) arrayOf(it) else null }
|
||||
override fun getStreamTypes(uri: Uri, mimeTypeFilter: String): Array<String>? =
|
||||
getType(uri)?.let { if (compareMimeTypes(it, mimeTypeFilter)) arrayOf(it) else null }
|
||||
|
||||
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
|
||||
if (mode != "r") return null
|
||||
|
||||
@@ -77,6 +77,7 @@ class MainActivity : BaseActivity(), FragmentManager.OnBackStackChangedListener
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.menu_action_edit -> {
|
||||
supportFragmentManager.commit {
|
||||
replace(R.id.detail_container, TunnelEditorFragment())
|
||||
@@ -91,12 +92,15 @@ class MainActivity : BaseActivity(), FragmentManager.OnBackStackChangedListener
|
||||
startActivity(Intent(this, SettingsActivity::class.java))
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?,
|
||||
newTunnel: ObservableTunnel?): Boolean {
|
||||
override fun onSelectedTunnelChanged(
|
||||
oldTunnel: ObservableTunnel?,
|
||||
newTunnel: ObservableTunnel?
|
||||
): Boolean {
|
||||
val fragmentManager = supportFragmentManager
|
||||
if (fragmentManager.isStateSaved) {
|
||||
return false
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
*/
|
||||
package com.wireguard.android.activity
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.service.quicksettings.TileService
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.commit
|
||||
@@ -14,6 +16,7 @@ import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.QuickTileService
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.backend.WgQuickBackend
|
||||
import com.wireguard.android.preference.PreferencesPreferenceDataStore
|
||||
@@ -47,7 +50,13 @@ class SettingsActivity : AppCompatActivity() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, key: String?) {
|
||||
preferenceManager.preferenceDataStore = PreferencesPreferenceDataStore(lifecycleScope, Application.getPreferencesDataStore())
|
||||
addPreferencesFromResource(R.xml.preferences)
|
||||
preferenceScreen.initialExpandedChildrenCount = 4
|
||||
preferenceScreen.initialExpandedChildrenCount = 5
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || QuickTileService.isAdded) {
|
||||
val quickTile = preferenceManager.findPreference<Preference>("quick_tile")
|
||||
quickTile?.parent?.removePreference(quickTile)
|
||||
--preferenceScreen.initialExpandedChildrenCount
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
val darkTheme = preferenceManager.findPreference<Preference>("dark_theme")
|
||||
darkTheme?.parent?.removePreference(darkTheme)
|
||||
@@ -62,9 +71,9 @@ class SettingsActivity : AppCompatActivity() {
|
||||
zipExporter?.parent?.removePreference(zipExporter)
|
||||
}
|
||||
val wgQuickOnlyPrefs = arrayOf(
|
||||
preferenceManager.findPreference("tools_installer"),
|
||||
preferenceManager.findPreference("restore_on_boot"),
|
||||
preferenceManager.findPreference<Preference>("multiple_tunnels")
|
||||
preferenceManager.findPreference("tools_installer"),
|
||||
preferenceManager.findPreference("restore_on_boot"),
|
||||
preferenceManager.findPreference<Preference>("multiple_tunnels")
|
||||
).filterNotNull()
|
||||
wgQuickOnlyPrefs.forEach { it.isVisible = false }
|
||||
lifecycleScope.launch {
|
||||
|
||||
@@ -24,7 +24,8 @@ import kotlinx.coroutines.launch
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
class TunnelToggleActivity : AppCompatActivity() {
|
||||
private val permissionActivityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { toggleTunnelWithPermissionsResult() }
|
||||
private val permissionActivityResultLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { toggleTunnelWithPermissionsResult() }
|
||||
|
||||
private fun toggleTunnelWithPermissionsResult() {
|
||||
val tunnel = Application.getTunnelManager().lastUsedTunnel ?: return
|
||||
|
||||
@@ -211,12 +211,13 @@ class TvMainActivity : AppCompatActivity() {
|
||||
try {
|
||||
tunnelFileImportResultLauncher.launch("*/*")
|
||||
} catch (_: Throwable) {
|
||||
MaterialAlertDialogBuilder(binding.root.context).setMessage(R.string.tv_no_file_picker).setCancelable(false).setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
try {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://webstoreredirect")))
|
||||
} catch (_: Throwable) {
|
||||
}
|
||||
}.show()
|
||||
MaterialAlertDialogBuilder(binding.root.context).setMessage(R.string.tv_no_file_picker).setCancelable(false)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
try {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://webstoreredirect")))
|
||||
} catch (_: Throwable) {
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -359,6 +360,7 @@ class TvMainActivity : AppCompatActivity() {
|
||||
binding.tunnelList.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
filesRoot.get()?.isNotEmpty() == true -> {
|
||||
files.clear()
|
||||
filesRoot.set("")
|
||||
@@ -372,7 +374,7 @@ class TvMainActivity : AppCompatActivity() {
|
||||
private suspend fun updateStats() {
|
||||
binding.tunnelList.forEach { viewItem ->
|
||||
val listItem = DataBindingUtil.findBinding<TvTunnelListItemBinding>(viewItem)
|
||||
?: return@forEach
|
||||
?: return@forEach
|
||||
try {
|
||||
val tunnel = listItem.item!!
|
||||
if (tunnel.state != Tunnel.State.UP || isDeleting.get()) {
|
||||
|
||||
@@ -40,9 +40,9 @@ class FileConfigStore(private val context: Context) : ConfigStore {
|
||||
|
||||
override fun enumerate(): Set<String> {
|
||||
return context.fileList()
|
||||
.filter { it.endsWith(".conf") }
|
||||
.map { it.substring(0, it.length - ".conf".length) }
|
||||
.toSet()
|
||||
.filter { it.endsWith(".conf") }
|
||||
.map { it.substring(0, it.length - ".conf".length) }
|
||||
.toSet()
|
||||
}
|
||||
|
||||
private fun fileFor(name: String): File {
|
||||
|
||||
@@ -47,9 +47,11 @@ object BindingAdapters {
|
||||
|
||||
@JvmStatic
|
||||
@BindingAdapter("items", "layout", "fragment")
|
||||
fun <E> setItems(view: LinearLayout,
|
||||
oldList: ObservableList<E>?, oldLayoutId: Int, @Suppress("UNUSED_PARAMETER") oldFragment: Fragment?,
|
||||
newList: ObservableList<E>?, newLayoutId: Int, newFragment: Fragment?) {
|
||||
fun <E> setItems(
|
||||
view: LinearLayout,
|
||||
oldList: ObservableList<E>?, oldLayoutId: Int, @Suppress("UNUSED_PARAMETER") oldFragment: Fragment?,
|
||||
newList: ObservableList<E>?, newLayoutId: Int, newFragment: Fragment?
|
||||
) {
|
||||
if (oldList === newList && oldLayoutId == newLayoutId)
|
||||
return
|
||||
var listener: ItemChangeListener<E>? = ListenerUtil.getListener(view, R.id.item_change_listener)
|
||||
@@ -73,9 +75,11 @@ object BindingAdapters {
|
||||
|
||||
@JvmStatic
|
||||
@BindingAdapter("items", "layout")
|
||||
fun <E> setItems(view: LinearLayout,
|
||||
oldList: Iterable<E>?, oldLayoutId: Int,
|
||||
newList: Iterable<E>?, newLayoutId: Int) {
|
||||
fun <E> setItems(
|
||||
view: LinearLayout,
|
||||
oldList: Iterable<E>?, oldLayoutId: Int,
|
||||
newList: Iterable<E>?, newLayoutId: Int
|
||||
) {
|
||||
if (oldList === newList && oldLayoutId == newLayoutId)
|
||||
return
|
||||
view.removeAllViews()
|
||||
@@ -93,11 +97,13 @@ object BindingAdapters {
|
||||
|
||||
@JvmStatic
|
||||
@BindingAdapter(requireAll = false, value = ["items", "layout", "configurationHandler"])
|
||||
fun <K, E : Keyed<out K>> setItems(view: RecyclerView,
|
||||
oldList: ObservableKeyedArrayList<K, E>?, oldLayoutId: Int,
|
||||
@Suppress("UNUSED_PARAMETER") oldRowConfigurationHandler: RowConfigurationHandler<*, *>?,
|
||||
newList: ObservableKeyedArrayList<K, E>?, newLayoutId: Int,
|
||||
newRowConfigurationHandler: RowConfigurationHandler<*, *>?) {
|
||||
fun <K, E : Keyed<out K>> setItems(
|
||||
view: RecyclerView,
|
||||
oldList: ObservableKeyedArrayList<K, E>?, oldLayoutId: Int,
|
||||
@Suppress("UNUSED_PARAMETER") oldRowConfigurationHandler: RowConfigurationHandler<*, *>?,
|
||||
newList: ObservableKeyedArrayList<K, E>?, newLayoutId: Int,
|
||||
newRowConfigurationHandler: RowConfigurationHandler<*, *>?
|
||||
) {
|
||||
if (view.layoutManager == null)
|
||||
view.layoutManager = LinearLayoutManager(view.context, RecyclerView.VERTICAL, false)
|
||||
if (oldList === newList && oldLayoutId == newLayoutId)
|
||||
@@ -123,16 +129,20 @@ object BindingAdapters {
|
||||
|
||||
@JvmStatic
|
||||
@BindingAdapter("onBeforeCheckedChanged")
|
||||
fun setOnBeforeCheckedChanged(view: ToggleSwitch,
|
||||
listener: OnBeforeCheckedChangeListener?) {
|
||||
fun setOnBeforeCheckedChanged(
|
||||
view: ToggleSwitch,
|
||||
listener: OnBeforeCheckedChangeListener?
|
||||
) {
|
||||
view.setOnBeforeCheckedChangeListener(listener)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@BindingAdapter("onFocusChange")
|
||||
fun setOnFocusChange(view: EditText,
|
||||
listener: View.OnFocusChangeListener?) {
|
||||
view.setOnFocusChangeListener(listener)
|
||||
fun setOnFocusChange(
|
||||
view: EditText,
|
||||
listener: View.OnFocusChangeListener?
|
||||
) {
|
||||
view.onFocusChangeListener = listener
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
|
||||
@@ -61,8 +61,10 @@ internal class ItemChangeListener<T>(private val container: ViewGroup, private v
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemRangeChanged(sender: ObservableList<T>, positionStart: Int,
|
||||
itemCount: Int) {
|
||||
override fun onItemRangeChanged(
|
||||
sender: ObservableList<T>, positionStart: Int,
|
||||
itemCount: Int
|
||||
) {
|
||||
val listener = weakListener.get()
|
||||
if (listener != null) {
|
||||
for (i in positionStart until positionStart + itemCount) {
|
||||
@@ -75,8 +77,10 @@ internal class ItemChangeListener<T>(private val container: ViewGroup, private v
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemRangeInserted(sender: ObservableList<T>, positionStart: Int,
|
||||
itemCount: Int) {
|
||||
override fun onItemRangeInserted(
|
||||
sender: ObservableList<T>, positionStart: Int,
|
||||
itemCount: Int
|
||||
) {
|
||||
val listener = weakListener.get()
|
||||
if (listener != null) {
|
||||
for (i in positionStart until positionStart + itemCount)
|
||||
@@ -86,8 +90,10 @@ internal class ItemChangeListener<T>(private val container: ViewGroup, private v
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemRangeMoved(sender: ObservableList<T>, fromPosition: Int,
|
||||
toPosition: Int, itemCount: Int) {
|
||||
override fun onItemRangeMoved(
|
||||
sender: ObservableList<T>, fromPosition: Int,
|
||||
toPosition: Int, itemCount: Int
|
||||
) {
|
||||
val listener = weakListener.get()
|
||||
if (listener != null) {
|
||||
val views = arrayOfNulls<View>(itemCount)
|
||||
@@ -99,8 +105,10 @@ internal class ItemChangeListener<T>(private val container: ViewGroup, private v
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemRangeRemoved(sender: ObservableList<T>, positionStart: Int,
|
||||
itemCount: Int) {
|
||||
override fun onItemRangeRemoved(
|
||||
sender: ObservableList<T>, positionStart: Int,
|
||||
itemCount: Int
|
||||
) {
|
||||
val listener = weakListener.get()
|
||||
if (listener != null) {
|
||||
listener.container.removeViews(positionStart, itemCount)
|
||||
|
||||
@@ -49,7 +49,8 @@ class AppListDialogFragment : DialogFragment() {
|
||||
packageInfos.forEach {
|
||||
val packageName = it.packageName
|
||||
val appInfo = it.applicationInfo
|
||||
val appData = ApplicationData(appInfo.loadIcon(pm), appInfo.loadLabel(pm).toString(), packageName, currentlySelectedApps.contains(packageName))
|
||||
val appData =
|
||||
ApplicationData(appInfo.loadIcon(pm), appInfo.loadLabel(pm).toString(), packageName, currentlySelectedApps.contains(packageName))
|
||||
applicationData.add(appData)
|
||||
appData.addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {
|
||||
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
|
||||
@@ -63,8 +64,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]
|
||||
@@ -143,10 +144,12 @@ class AppListDialogFragment : DialogFragment() {
|
||||
selectedApps.add(data.packageName)
|
||||
}
|
||||
}
|
||||
setFragmentResult(REQUEST_SELECTION, bundleOf(
|
||||
setFragmentResult(
|
||||
REQUEST_SELECTION, bundleOf(
|
||||
KEY_SELECTED_APPS to selectedApps.toTypedArray(),
|
||||
KEY_IS_EXCLUDED to (tabs?.selectedTabPosition == 0)
|
||||
))
|
||||
)
|
||||
)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
|
||||
@@ -99,8 +99,8 @@ abstract class BaseFragment : Fragment(), OnSelectedTunnelChangedListener {
|
||||
val view = view
|
||||
if (view != null)
|
||||
Snackbar.make(view, message, Snackbar.LENGTH_LONG)
|
||||
.setAnchorView(view.findViewById(R.id.create_fab))
|
||||
.show()
|
||||
.setAnchorView(view.findViewById(R.id.create_fab))
|
||||
.show()
|
||||
else
|
||||
Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
|
||||
Log.e(TAG, message, e)
|
||||
|
||||
@@ -37,6 +37,7 @@ class ConfigNamingDialogFragment : DialogFragment() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val configText = requireArguments().getString(KEY_CONFIG_TEXT)
|
||||
|
||||
@@ -40,8 +40,10 @@ class TunnelDetailFragment : BaseFragment(), MenuProvider {
|
||||
menuInflater.inflate(R.menu.tunnel_detail, menu)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
super.onCreateView(inflater, container, savedInstanceState)
|
||||
binding = TunnelDetailFragmentBinding.inflate(inflater, container, false)
|
||||
binding?.executePendingBindings()
|
||||
@@ -110,25 +112,38 @@ class TunnelDetailFragment : BaseFragment(), MenuProvider {
|
||||
val statistics = tunnel.getStatisticsAsync()
|
||||
for (i in 0 until binding.peersLayout.childCount) {
|
||||
val peer: TunnelDetailPeerBinding = DataBindingUtil.getBinding(binding.peersLayout.getChildAt(i))
|
||||
?: continue
|
||||
?: 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) {
|
||||
val peer: TunnelDetailPeerBinding = DataBindingUtil.getBinding(binding.peersLayout.getChildAt(i))
|
||||
?: continue
|
||||
?: continue
|
||||
peer.transferLabel.visibility = View.GONE
|
||||
peer.transferText.visibility = View.GONE
|
||||
peer.latestHandshakeLabel.visibility = View.GONE
|
||||
peer.latestHandshakeText.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
package com.wireguard.android.fragment
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.InputType
|
||||
import android.util.Log
|
||||
@@ -67,16 +66,14 @@ class TunnelEditorFragment : BaseFragment(), MenuProvider {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menuInflater.inflate(R.menu.config_editor, menu)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
super.onCreateView(inflater, container, savedInstanceState)
|
||||
binding = TunnelEditorFragmentBinding.inflate(inflater, container, false)
|
||||
binding?.apply {
|
||||
@@ -103,8 +100,10 @@ class TunnelEditorFragment : BaseFragment(), MenuProvider {
|
||||
val focusedView = activity.currentFocus
|
||||
if (focusedView != null) {
|
||||
val inputManager = activity.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
||||
inputManager?.hideSoftInputFromWindow(focusedView.windowToken,
|
||||
InputMethodManager.HIDE_NOT_ALWAYS)
|
||||
inputManager?.hideSoftInputFromWindow(
|
||||
focusedView.windowToken,
|
||||
InputMethodManager.HIDE_NOT_ALWAYS
|
||||
)
|
||||
}
|
||||
parentFragmentManager.popBackStackImmediate()
|
||||
|
||||
@@ -138,6 +137,7 @@ class TunnelEditorFragment : BaseFragment(), MenuProvider {
|
||||
onTunnelCreated(null, e)
|
||||
}
|
||||
}
|
||||
|
||||
tunnel!!.name != binding!!.name -> {
|
||||
Log.d(TAG, "Attempting to rename tunnel to " + binding!!.name)
|
||||
try {
|
||||
@@ -147,6 +147,7 @@ class TunnelEditorFragment : BaseFragment(), MenuProvider {
|
||||
onTunnelRenamed(tunnel!!, newConfig, e)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
Log.d(TAG, "Attempting to save config of " + tunnel!!.name)
|
||||
try {
|
||||
@@ -202,8 +203,10 @@ class TunnelEditorFragment : BaseFragment(), MenuProvider {
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
override fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?,
|
||||
newTunnel: ObservableTunnel?) {
|
||||
override fun onSelectedTunnelChanged(
|
||||
oldTunnel: ObservableTunnel?,
|
||||
newTunnel: ObservableTunnel?
|
||||
) {
|
||||
tunnel = newTunnel
|
||||
if (binding == null) return
|
||||
binding!!.config = ConfigProxy()
|
||||
@@ -240,8 +243,10 @@ class TunnelEditorFragment : BaseFragment(), MenuProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun onTunnelRenamed(renamedTunnel: ObservableTunnel, newConfig: Config,
|
||||
throwable: Throwable?) {
|
||||
private suspend fun onTunnelRenamed(
|
||||
renamedTunnel: ObservableTunnel, newConfig: Config,
|
||||
throwable: Throwable?
|
||||
) {
|
||||
val ctx = activity ?: Application.get()
|
||||
if (throwable == null) {
|
||||
val message = ctx.getString(R.string.tunnel_rename_success, renamedTunnel.name)
|
||||
@@ -298,13 +303,15 @@ class TunnelEditorFragment : BaseFragment(), MenuProvider {
|
||||
haveShownKeys = true
|
||||
showPrivateKey(edit)
|
||||
}
|
||||
|
||||
is BiometricAuthenticator.Result.Failure -> {
|
||||
Snackbar.make(
|
||||
binding!!.mainContainer,
|
||||
it.message,
|
||||
Snackbar.LENGTH_SHORT
|
||||
binding!!.mainContainer,
|
||||
it.message,
|
||||
Snackbar.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
is BiometricAuthenticator.Result.Cancelled -> {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -80,6 +81,8 @@ class TunnelListFragment : BaseFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private val snackbarUpdateShower = SnackbarUpdateShower(this)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
if (savedInstanceState != null) {
|
||||
@@ -90,8 +93,10 @@ class TunnelListFragment : BaseFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
super.onCreateView(inflater, container, savedInstanceState)
|
||||
binding = TunnelListFragmentBinding.inflate(inflater, container, false)
|
||||
val bottomSheet = AddTunnelsSheet()
|
||||
@@ -104,20 +109,25 @@ class TunnelListFragment : BaseFragment() {
|
||||
AddTunnelsSheet.REQUEST_CREATE -> {
|
||||
startActivity(Intent(requireActivity(), TunnelCreatorActivity::class.java))
|
||||
}
|
||||
|
||||
AddTunnelsSheet.REQUEST_IMPORT -> {
|
||||
tunnelFileImportResultLauncher.launch("*/*")
|
||||
}
|
||||
|
||||
AddTunnelsSheet.REQUEST_SCAN -> {
|
||||
qrImportResultLauncher.launch(ScanOptions()
|
||||
qrImportResultLauncher.launch(
|
||||
ScanOptions()
|
||||
.setOrientationLocked(false)
|
||||
.setBeepEnabled(false)
|
||||
.setPrompt(getString(R.string.qr_code_hint)))
|
||||
.setPrompt(getString(R.string.qr_code_hint))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
bottomSheet.showNow(childFragmentManager, "BOTTOM_SHEET")
|
||||
}
|
||||
executePendingBindings()
|
||||
snackbarUpdateShower.attach(mainContainer, createFab)
|
||||
}
|
||||
backPressedCallback = requireActivity().onBackPressedDispatcher.addCallback(this) { actionMode?.finish() }
|
||||
backPressedCallback?.isEnabled = false
|
||||
@@ -188,8 +198,8 @@ class TunnelListFragment : BaseFragment() {
|
||||
val binding = binding
|
||||
if (binding != null)
|
||||
Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG)
|
||||
.setAnchorView(binding.createFab)
|
||||
.show()
|
||||
.setAnchorView(binding.createFab)
|
||||
.show()
|
||||
else
|
||||
Toast.makeText(activity ?: Application.get(), message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
@@ -231,6 +241,7 @@ class TunnelListFragment : BaseFragment() {
|
||||
mode.finish()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.menu_action_select_all -> {
|
||||
lifecycleScope.launch {
|
||||
val tunnels = Application.getTunnelManager().getTunnels()
|
||||
@@ -240,6 +251,7 @@ class TunnelListFragment : BaseFragment() {
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@@ -305,7 +317,7 @@ class TunnelListFragment : BaseFragment() {
|
||||
private fun animateFab(view: View?, show: Boolean) {
|
||||
view ?: return
|
||||
val animation = AnimationUtils.loadAnimation(
|
||||
context, if (show) R.anim.scale_up else R.anim.scale_down
|
||||
context, if (show) R.anim.scale_up else R.anim.scale_down
|
||||
)
|
||||
animation.setAnimationListener(object : Animation.AnimationListener {
|
||||
override fun onAnimationRepeat(animation: Animation?) {
|
||||
|
||||
@@ -21,10 +21,10 @@ import kotlinx.coroutines.withContext
|
||||
* Encapsulates the volatile and nonvolatile state of a WireGuard tunnel.
|
||||
*/
|
||||
class ObservableTunnel internal constructor(
|
||||
private val manager: TunnelManager,
|
||||
private var name: String,
|
||||
config: Config?,
|
||||
state: Tunnel.State
|
||||
private val manager: TunnelManager,
|
||||
private var name: String,
|
||||
config: Config?,
|
||||
state: Tunnel.State
|
||||
) : BaseObservable(), Keyed<String>, Tunnel {
|
||||
override val key
|
||||
get() = name
|
||||
|
||||
@@ -140,7 +140,8 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
|
||||
if (previouslyRunning.isEmpty()) return
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
tunnelMap.filter { previouslyRunning.contains(it.name) }.map { async(Dispatchers.IO + SupervisorJob()) { setTunnelState(it, Tunnel.State.UP) } }.awaitAll()
|
||||
tunnelMap.filter { previouslyRunning.contains(it.name) }.map { async(Dispatchers.IO + SupervisorJob()) { setTunnelState(it, Tunnel.State.UP) } }
|
||||
.awaitAll()
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, Log.getStackTraceString(e))
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@ 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.R
|
||||
import com.wireguard.android.updater.Updater
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
|
||||
class DonatePreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
|
||||
@@ -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 (Updater.installerIsGooglePlay(context)) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.donate_title)
|
||||
.setMessage(R.string.donate_google_play_disappointment)
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.preference
|
||||
|
||||
import android.app.StatusBarManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Icon
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.preference.Preference
|
||||
import com.wireguard.android.QuickTileService
|
||||
import com.wireguard.android.R
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||
class QuickTilePreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
|
||||
override fun getSummary() = context.getString(R.string.quick_settings_tile_add_summary)
|
||||
|
||||
override fun getTitle() = context.getString(R.string.quick_settings_tile_add_title)
|
||||
|
||||
override fun onClick() {
|
||||
val statusBarManager = context.getSystemService(StatusBarManager::class.java)
|
||||
statusBarManager.requestAddTileService(
|
||||
ComponentName(context, QuickTileService::class.java),
|
||||
context.getString(R.string.quick_settings_tile_action),
|
||||
Icon.createWithResource(context, R.drawable.ic_tile),
|
||||
context.mainExecutor
|
||||
) {
|
||||
when (it) {
|
||||
StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED,
|
||||
StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED -> {
|
||||
parent?.removePreference(this)
|
||||
--preferenceManager.preferenceScreen.initialExpandedChildrenCount
|
||||
}
|
||||
StatusBarManager.TILE_ADD_REQUEST_ERROR_MISMATCHED_PACKAGE,
|
||||
StatusBarManager.TILE_ADD_REQUEST_ERROR_REQUEST_IN_PROGRESS,
|
||||
StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT,
|
||||
StatusBarManager.TILE_ADD_REQUEST_ERROR_NOT_CURRENT_USER,
|
||||
StatusBarManager.TILE_ADD_REQUEST_ERROR_APP_NOT_IN_FOREGROUND,
|
||||
StatusBarManager.TILE_ADD_REQUEST_ERROR_NO_STATUS_BAR_SERVICE ->
|
||||
Toast.makeText(context, context.getString(R.string.quick_settings_tile_add_failure, it), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,14 +70,16 @@ class ZipExporterPreference(context: Context, attrs: AttributeSet?) : Preference
|
||||
val message = context.getString(R.string.zip_export_error, error)
|
||||
Log.e(TAG, message, e)
|
||||
Snackbar.make(
|
||||
activity.findViewById(android.R.id.content),
|
||||
message, Snackbar.LENGTH_LONG).show()
|
||||
activity.findViewById(android.R.id.content),
|
||||
message, Snackbar.LENGTH_LONG
|
||||
).show()
|
||||
isEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSummary() = if (exportedFilePath == null) context.getString(R.string.zip_export_summary) else context.getString(R.string.zip_export_success, exportedFilePath)
|
||||
override fun getSummary() =
|
||||
if (exportedFilePath == null) context.getString(R.string.zip_export_summary) else context.getString(R.string.zip_export_success, exportedFilePath)
|
||||
|
||||
override fun getTitle() = context.getString(R.string.zip_export_title)
|
||||
|
||||
@@ -91,13 +93,15 @@ class ZipExporterPreference(context: Context, attrs: AttributeSet?) : Preference
|
||||
isEnabled = false
|
||||
exportZip()
|
||||
}
|
||||
|
||||
is BiometricAuthenticator.Result.Failure -> {
|
||||
Snackbar.make(
|
||||
activity.findViewById(android.R.id.content),
|
||||
it.message,
|
||||
Snackbar.LENGTH_SHORT
|
||||
activity.findViewById(android.R.id.content),
|
||||
it.message,
|
||||
Snackbar.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
is BiometricAuthenticator.Result.Cancelled -> {}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.updater
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.BaseTransientBottomBar
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
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
|
||||
|
||||
class SnackbarUpdateShower(private val fragment: Fragment) {
|
||||
private var lastUserIntervention: Updater.Progress.NeedsUserIntervention? = null
|
||||
private val intentLauncher = fragment.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
lastUserIntervention?.markAsDone()
|
||||
}
|
||||
|
||||
private class SwapableSnackbar(fragment: Fragment, view: View, anchor: View?) {
|
||||
private val actionSnackbar = makeSnackbar(fragment, view, anchor)
|
||||
private val statusSnackbar = makeSnackbar(fragment, view, anchor)
|
||||
private var showingAction: Boolean = false
|
||||
private var showingStatus: Boolean = false
|
||||
|
||||
private fun makeSnackbar(fragment: Fragment, view: View, anchor: View?): Snackbar {
|
||||
val snackbar = Snackbar.make(fragment.requireContext(), 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
|
||||
fragment.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 attach(view: View, anchor: View?) {
|
||||
val snackbar = SwapableSnackbar(fragment, view, anchor)
|
||||
val context = fragment.requireContext()
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
is Updater.Progress.Corrupt -> {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.updater_corrupt_title)
|
||||
.setMessage(R.string.updater_corrupt_message)
|
||||
.setPositiveButton(R.string.updater_corrupt_navigate) { _, _ ->
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = Uri.parse(progress.downloadUrl)
|
||||
try {
|
||||
context.startActivity(intent)
|
||||
} catch (e: Throwable) {
|
||||
Toast.makeText(context, ErrorMessages[e], Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}.setCancelable(false).setOnDismissListener {
|
||||
val intent = Intent(Intent.ACTION_MAIN)
|
||||
intent.addCategory(Intent.CATEGORY_HOME)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
context.startActivity(intent)
|
||||
System.exit(0)
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
}.launchIn(fragment.lifecycleScope)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,451 @@
|
||||
/*
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.updater
|
||||
|
||||
import android.Manifest
|
||||
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.content.pm.PackageManager
|
||||
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 com.wireguard.android.util.applicationScope
|
||||
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 UPDATE_URL_FMT = "https://download.wireguard.com/android-client/%s"
|
||||
private const val APK_NAME_PREFIX = BuildConfig.APPLICATION_ID + "-"
|
||||
private const val APK_NAME_SUFFIX = ".apk"
|
||||
private const val LATEST_FILE = "latest.sig"
|
||||
private const val RELEASE_PUBLIC_KEY_BASE64 = "RWTAzwGRYr3EC9px0Ia3fbttz8WcVN6wrOwWp2delz4el6SI8XmkKSMp"
|
||||
private val CURRENT_VERSION by lazy { Version(BuildConfig.VERSION_NAME) }
|
||||
|
||||
private val updaterScope = CoroutineScope(Job() + Dispatchers.IO)
|
||||
|
||||
private fun installer(context: Context): String = 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) {
|
||||
""
|
||||
}
|
||||
|
||||
fun installerIsGooglePlay(context: Context): Boolean = installer(context) == "com.android.vending"
|
||||
|
||||
sealed class Progress {
|
||||
object Complete : Progress()
|
||||
class Available(val version: String) : Progress() {
|
||||
fun update() {
|
||||
applicationScope.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() {
|
||||
applicationScope.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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Corrupt(private val betterFile: String?) : Progress() {
|
||||
val downloadUrl: String
|
||||
get() = UPDATE_URL_FMT.format(betterFile ?: "")
|
||||
}
|
||||
}
|
||||
|
||||
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(UPDATE_URL_FMT.format(LATEST_FILE)).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(UPDATE_URL_FMT.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 var updating = false
|
||||
private suspend fun downloadAndUpdateWrapErrors() {
|
||||
if (updating)
|
||||
return
|
||||
updating = true
|
||||
try {
|
||||
downloadAndUpdate()
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, "Update failure", e)
|
||||
emitProgress(Progress.Failure(e))
|
||||
}
|
||||
updating = false
|
||||
}
|
||||
|
||||
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)!!
|
||||
applicationScope.launch {
|
||||
emitProgress(Progress.NeedsUserIntervention(userIntervention, id))
|
||||
}
|
||||
}
|
||||
|
||||
PackageInstaller.STATUS_SUCCESS -> {
|
||||
applicationScope.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"
|
||||
applicationScope.launch {
|
||||
val e = Exception(message)
|
||||
Log.e(TAG, "Update failure", e)
|
||||
emitProgress(Progress.Failure(e))
|
||||
}
|
||||
context.applicationContext.unregisterReceiver(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun monitorForUpdates() {
|
||||
if (BuildConfig.DEBUG)
|
||||
return
|
||||
|
||||
val context = Application.get()
|
||||
|
||||
if (installerIsGooglePlay(context))
|
||||
return
|
||||
|
||||
if (!if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
@Suppress("DEPRECATION")
|
||||
context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_PERMISSIONS)
|
||||
} else {
|
||||
context.packageManager.getPackageInfo(context.packageName, PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS.toLong()))
|
||||
}.requestedPermissions.contains(Manifest.permission.REQUEST_INSTALL_PACKAGES)
|
||||
) {
|
||||
if (installer(context).isNotEmpty()) {
|
||||
updaterScope.launch {
|
||||
val update = try {
|
||||
checkForUpdates()
|
||||
} catch (_: Throwable) {
|
||||
null
|
||||
}
|
||||
emitProgress(Progress.Corrupt(update?.fileName))
|
||||
}
|
||||
}
|
||||
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(applicationScope)
|
||||
|
||||
UserKnobs.updaterNewerVersionConsented.onEach { ver ->
|
||||
if (ver != null && Version(ver) > CURRENT_VERSION)
|
||||
updaterScope.launch {
|
||||
downloadAndUpdateWrapErrors()
|
||||
}
|
||||
}.launchIn(applicationScope)
|
||||
}
|
||||
|
||||
class AppUpdatedReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action != Intent.ACTION_MY_PACKAGE_REPLACED)
|
||||
return
|
||||
|
||||
if (installer(context) != 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,5 +13,5 @@ object AdminKnobs {
|
||||
private val restrictions: RestrictionsManager? = Application.get().getSystemService()
|
||||
val disableConfigExport: Boolean
|
||||
get() = restrictions?.applicationRestrictions?.getBoolean("disable_config_export", false)
|
||||
?: false
|
||||
?: false
|
||||
}
|
||||
|
||||
@@ -31,25 +31,29 @@ object BiometricAuthenticator {
|
||||
}
|
||||
|
||||
fun authenticate(
|
||||
@StringRes dialogTitleRes: Int,
|
||||
fragment: Fragment,
|
||||
callback: (Result) -> Unit
|
||||
@StringRes dialogTitleRes: Int,
|
||||
fragment: Fragment,
|
||||
callback: (Result) -> Unit
|
||||
) {
|
||||
val authCallback = object : BiometricPrompt.AuthenticationCallback() {
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
super.onAuthenticationError(errorCode, errString)
|
||||
Log.d(TAG, "BiometricAuthentication error: errorCode=$errorCode, msg=$errString")
|
||||
callback(when (errorCode) {
|
||||
BiometricPrompt.ERROR_CANCELED, BiometricPrompt.ERROR_USER_CANCELED,
|
||||
BiometricPrompt.ERROR_NEGATIVE_BUTTON -> {
|
||||
Result.Cancelled
|
||||
callback(
|
||||
when (errorCode) {
|
||||
BiometricPrompt.ERROR_CANCELED, BiometricPrompt.ERROR_USER_CANCELED,
|
||||
BiometricPrompt.ERROR_NEGATIVE_BUTTON -> {
|
||||
Result.Cancelled
|
||||
}
|
||||
|
||||
BiometricPrompt.ERROR_HW_NOT_PRESENT, BiometricPrompt.ERROR_HW_UNAVAILABLE,
|
||||
BiometricPrompt.ERROR_NO_BIOMETRICS, BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> {
|
||||
Result.HardwareUnavailableOrDisabled
|
||||
}
|
||||
|
||||
else -> Result.Failure(errorCode, fragment.getString(R.string.biometric_auth_error_reason, errString))
|
||||
}
|
||||
BiometricPrompt.ERROR_HW_NOT_PRESENT, BiometricPrompt.ERROR_HW_UNAVAILABLE,
|
||||
BiometricPrompt.ERROR_NO_BIOMETRICS, BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> {
|
||||
Result.HardwareUnavailableOrDisabled
|
||||
}
|
||||
else -> Result.Failure(errorCode, fragment.getString(R.string.biometric_auth_error_reason, errString))
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
override fun onAuthenticationFailed() {
|
||||
@@ -64,9 +68,9 @@ object BiometricAuthenticator {
|
||||
}
|
||||
val biometricPrompt = BiometricPrompt(fragment, { Handler(Looper.getMainLooper()).post(it) }, authCallback)
|
||||
val promptInfo = BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle(fragment.getString(dialogTitleRes))
|
||||
.setAllowedAuthenticators(allowedAuthenticators)
|
||||
.build()
|
||||
.setTitle(fragment.getString(dialogTitleRes))
|
||||
.setAllowedAuthenticators(allowedAuthenticators)
|
||||
.build()
|
||||
if (BiometricManager.from(fragment.requireContext()).canAuthenticate(allowedAuthenticators) == BiometricManager.BIOMETRIC_SUCCESS) {
|
||||
biometricPrompt.authenticate(promptInfo)
|
||||
} else {
|
||||
|
||||
@@ -46,9 +46,9 @@ class DownloadsFileSaver(private val context: ComponentActivity) {
|
||||
contentValues.put(MediaColumns.DISPLAY_NAME, name)
|
||||
contentValues.put(MediaColumns.MIME_TYPE, mimeType)
|
||||
val contentUri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
|
||||
?: throw IOException(context.getString(R.string.create_downloads_file_error))
|
||||
?: throw IOException(context.getString(R.string.create_downloads_file_error))
|
||||
val contentStream = contentResolver.openOutputStream(contentUri)
|
||||
?: throw IOException(context.getString(R.string.create_downloads_file_error))
|
||||
?: throw IOException(context.getString(R.string.create_downloads_file_error))
|
||||
@Suppress("DEPRECATION") var cursor = contentResolver.query(contentUri, arrayOf(MediaColumns.DATA), null, null, null)
|
||||
var path: String? = null
|
||||
if (cursor != null) {
|
||||
|
||||
@@ -22,47 +22,47 @@ import java.net.InetAddress
|
||||
|
||||
object ErrorMessages {
|
||||
private val BCE_REASON_MAP = mapOf(
|
||||
BadConfigException.Reason.INVALID_KEY to R.string.bad_config_reason_invalid_key,
|
||||
BadConfigException.Reason.INVALID_NUMBER to R.string.bad_config_reason_invalid_number,
|
||||
BadConfigException.Reason.INVALID_VALUE to R.string.bad_config_reason_invalid_value,
|
||||
BadConfigException.Reason.MISSING_ATTRIBUTE to R.string.bad_config_reason_missing_attribute,
|
||||
BadConfigException.Reason.MISSING_SECTION to R.string.bad_config_reason_missing_section,
|
||||
BadConfigException.Reason.SYNTAX_ERROR to R.string.bad_config_reason_syntax_error,
|
||||
BadConfigException.Reason.UNKNOWN_ATTRIBUTE to R.string.bad_config_reason_unknown_attribute,
|
||||
BadConfigException.Reason.UNKNOWN_SECTION to R.string.bad_config_reason_unknown_section
|
||||
BadConfigException.Reason.INVALID_KEY to R.string.bad_config_reason_invalid_key,
|
||||
BadConfigException.Reason.INVALID_NUMBER to R.string.bad_config_reason_invalid_number,
|
||||
BadConfigException.Reason.INVALID_VALUE to R.string.bad_config_reason_invalid_value,
|
||||
BadConfigException.Reason.MISSING_ATTRIBUTE to R.string.bad_config_reason_missing_attribute,
|
||||
BadConfigException.Reason.MISSING_SECTION to R.string.bad_config_reason_missing_section,
|
||||
BadConfigException.Reason.SYNTAX_ERROR to R.string.bad_config_reason_syntax_error,
|
||||
BadConfigException.Reason.UNKNOWN_ATTRIBUTE to R.string.bad_config_reason_unknown_attribute,
|
||||
BadConfigException.Reason.UNKNOWN_SECTION to R.string.bad_config_reason_unknown_section
|
||||
)
|
||||
private val BE_REASON_MAP = mapOf(
|
||||
BackendException.Reason.UNKNOWN_KERNEL_MODULE_NAME to R.string.module_version_error,
|
||||
BackendException.Reason.WG_QUICK_CONFIG_ERROR_CODE to R.string.tunnel_config_error,
|
||||
BackendException.Reason.TUNNEL_MISSING_CONFIG to R.string.no_config_error,
|
||||
BackendException.Reason.VPN_NOT_AUTHORIZED to R.string.vpn_not_authorized_error,
|
||||
BackendException.Reason.UNABLE_TO_START_VPN to R.string.vpn_start_error,
|
||||
BackendException.Reason.TUN_CREATION_ERROR to R.string.tun_create_error,
|
||||
BackendException.Reason.GO_ACTIVATION_ERROR_CODE to R.string.tunnel_on_error,
|
||||
BackendException.Reason.DNS_RESOLUTION_FAILURE to R.string.tunnel_dns_failure
|
||||
BackendException.Reason.UNKNOWN_KERNEL_MODULE_NAME to R.string.module_version_error,
|
||||
BackendException.Reason.WG_QUICK_CONFIG_ERROR_CODE to R.string.tunnel_config_error,
|
||||
BackendException.Reason.TUNNEL_MISSING_CONFIG to R.string.no_config_error,
|
||||
BackendException.Reason.VPN_NOT_AUTHORIZED to R.string.vpn_not_authorized_error,
|
||||
BackendException.Reason.UNABLE_TO_START_VPN to R.string.vpn_start_error,
|
||||
BackendException.Reason.TUN_CREATION_ERROR to R.string.tun_create_error,
|
||||
BackendException.Reason.GO_ACTIVATION_ERROR_CODE to R.string.tunnel_on_error,
|
||||
BackendException.Reason.DNS_RESOLUTION_FAILURE to R.string.tunnel_dns_failure
|
||||
)
|
||||
private val KFE_FORMAT_MAP = mapOf(
|
||||
Key.Format.BASE64 to R.string.key_length_explanation_base64,
|
||||
Key.Format.BINARY to R.string.key_length_explanation_binary,
|
||||
Key.Format.HEX to R.string.key_length_explanation_hex
|
||||
Key.Format.BASE64 to R.string.key_length_explanation_base64,
|
||||
Key.Format.BINARY to R.string.key_length_explanation_binary,
|
||||
Key.Format.HEX to R.string.key_length_explanation_hex
|
||||
)
|
||||
private val KFE_TYPE_MAP = mapOf(
|
||||
KeyFormatException.Type.CONTENTS to R.string.key_contents_error,
|
||||
KeyFormatException.Type.LENGTH to R.string.key_length_error
|
||||
KeyFormatException.Type.CONTENTS to R.string.key_contents_error,
|
||||
KeyFormatException.Type.LENGTH to R.string.key_length_error
|
||||
)
|
||||
private val PE_CLASS_MAP = mapOf(
|
||||
InetAddress::class.java to R.string.parse_error_inet_address,
|
||||
InetEndpoint::class.java to R.string.parse_error_inet_endpoint,
|
||||
InetNetwork::class.java to R.string.parse_error_inet_network,
|
||||
Int::class.java to R.string.parse_error_integer
|
||||
InetAddress::class.java to R.string.parse_error_inet_address,
|
||||
InetEndpoint::class.java to R.string.parse_error_inet_endpoint,
|
||||
InetNetwork::class.java to R.string.parse_error_inet_network,
|
||||
Int::class.java to R.string.parse_error_integer
|
||||
)
|
||||
private val RSE_REASON_MAP = mapOf(
|
||||
RootShellException.Reason.NO_ROOT_ACCESS to R.string.error_root,
|
||||
RootShellException.Reason.SHELL_MARKER_COUNT_ERROR to R.string.shell_marker_count_error,
|
||||
RootShellException.Reason.SHELL_EXIT_STATUS_READ_ERROR to R.string.shell_exit_status_read_error,
|
||||
RootShellException.Reason.SHELL_START_ERROR to R.string.shell_start_error,
|
||||
RootShellException.Reason.CREATE_BIN_DIR_ERROR to R.string.create_bin_dir_error,
|
||||
RootShellException.Reason.CREATE_TEMP_DIR_ERROR to R.string.create_temp_dir_error
|
||||
RootShellException.Reason.NO_ROOT_ACCESS to R.string.error_root,
|
||||
RootShellException.Reason.SHELL_MARKER_COUNT_ERROR to R.string.shell_marker_count_error,
|
||||
RootShellException.Reason.SHELL_EXIT_STATUS_READ_ERROR to R.string.shell_exit_status_read_error,
|
||||
RootShellException.Reason.SHELL_START_ERROR to R.string.shell_start_error,
|
||||
RootShellException.Reason.CREATE_BIN_DIR_ERROR to R.string.create_bin_dir_error,
|
||||
RootShellException.Reason.CREATE_TEMP_DIR_ERROR to R.string.create_temp_dir_error
|
||||
)
|
||||
|
||||
operator fun get(throwable: Throwable?): String {
|
||||
@@ -80,21 +80,27 @@ object ErrorMessages {
|
||||
val explanation = getBadConfigExceptionExplanation(resources, rootCause)
|
||||
resources.getString(R.string.bad_config_error, reason, context) + explanation
|
||||
}
|
||||
|
||||
rootCause is BackendException -> {
|
||||
resources.getString(BE_REASON_MAP.getValue(rootCause.reason), *rootCause.format)
|
||||
}
|
||||
|
||||
rootCause is RootShellException -> {
|
||||
resources.getString(RSE_REASON_MAP.getValue(rootCause.reason), *rootCause.format)
|
||||
}
|
||||
|
||||
rootCause is NotFoundException -> {
|
||||
resources.getString(R.string.error_no_qr_found)
|
||||
}
|
||||
|
||||
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
|
||||
resources.getString(R.string.generic_error, errorType)
|
||||
@@ -102,14 +108,16 @@ object ErrorMessages {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBadConfigExceptionExplanation(resources: Resources,
|
||||
bce: BadConfigException): String {
|
||||
private fun getBadConfigExceptionExplanation(
|
||||
resources: Resources,
|
||||
bce: BadConfigException
|
||||
): String {
|
||||
if (bce.cause is KeyFormatException) {
|
||||
val kfe = bce.cause as KeyFormatException?
|
||||
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) {
|
||||
@@ -120,8 +128,10 @@ object ErrorMessages {
|
||||
return ""
|
||||
}
|
||||
|
||||
private fun getBadConfigExceptionReason(resources: Resources,
|
||||
bce: BadConfigException): String {
|
||||
private fun getBadConfigExceptionReason(
|
||||
resources: Resources,
|
||||
bce: BadConfigException
|
||||
): String {
|
||||
if (bce.cause is KeyFormatException) {
|
||||
val kfe = bce.cause as KeyFormatException?
|
||||
return resources.getString(KFE_TYPE_MAP.getValue(kfe!!.type))
|
||||
@@ -137,7 +147,8 @@ object ErrorMessages {
|
||||
var cause = throwable
|
||||
while (cause.cause != null) {
|
||||
if (cause is BadConfigException || cause is BackendException ||
|
||||
cause is RootShellException) break
|
||||
cause is RootShellException
|
||||
) break
|
||||
val nextCause = cause.cause!!
|
||||
if (nextCause is RemoteException) break
|
||||
cause = nextCause
|
||||
|
||||
@@ -25,7 +25,7 @@ val Any.applicationScope: CoroutineScope
|
||||
|
||||
val Preference.activity: SettingsActivity
|
||||
get() = context as? SettingsActivity
|
||||
?: throw IllegalStateException("Failed to resolve SettingsActivity")
|
||||
?: throw IllegalStateException("Failed to resolve SettingsActivity")
|
||||
|
||||
val Preference.lifecycleScope: CoroutineScope
|
||||
get() = activity.lifecycleScope
|
||||
|
||||
@@ -55,11 +55,13 @@ class QrCodeFromFileScanner(
|
||||
multFactor = originalWidth.toFloat() / originalHeight.toFloat()
|
||||
newWidth = (newHeight * multFactor).toInt()
|
||||
}
|
||||
|
||||
originalWidth > originalHeight -> {
|
||||
newWidth = scaledSize
|
||||
multFactor = originalHeight.toFloat() / originalWidth.toFloat()
|
||||
newHeight = (newWidth * multFactor).toInt()
|
||||
}
|
||||
|
||||
originalHeight == originalWidth -> {
|
||||
newHeight = scaledSize
|
||||
newWidth = scaledSize
|
||||
|
||||
@@ -5,8 +5,16 @@
|
||||
|
||||
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
|
||||
|
||||
object QuantityFormatter {
|
||||
fun formatBytes(bytes: Long): String {
|
||||
@@ -19,4 +27,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)
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,6 @@ import java.io.BufferedReader
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStreamReader
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.ArrayList
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
@@ -135,12 +134,16 @@ object TunnelImporter {
|
||||
message = context.getString(R.string.import_success, tunnels[0].name)
|
||||
else if (tunnels.isEmpty() && throwables.size == 1)
|
||||
else if (throwables.isEmpty())
|
||||
message = context.resources.getQuantityString(R.plurals.import_total_success,
|
||||
tunnels.size, tunnels.size)
|
||||
message = context.resources.getQuantityString(
|
||||
R.plurals.import_total_success,
|
||||
tunnels.size, tunnels.size
|
||||
)
|
||||
else if (!throwables.isEmpty())
|
||||
message = context.resources.getQuantityString(R.plurals.import_partial_success,
|
||||
tunnels.size + throwables.size,
|
||||
tunnels.size, tunnels.size + throwables.size)
|
||||
message = context.resources.getQuantityString(
|
||||
R.plurals.import_partial_success,
|
||||
tunnels.size + throwables.size,
|
||||
tunnels.size, tunnels.size + throwables.size
|
||||
)
|
||||
|
||||
messageCallback(message)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,9 +55,9 @@ class ConfigProxy : Parcelable {
|
||||
val resolvedPeers: MutableCollection<Peer> = ArrayList()
|
||||
peers.forEach { resolvedPeers.add(it.resolve()) }
|
||||
return Config.Builder()
|
||||
.setInterface(`interface`.resolve())
|
||||
.addPeers(resolvedPeers)
|
||||
.build()
|
||||
.setInterface(`interface`.resolve())
|
||||
.addPeers(resolvedPeers)
|
||||
.build()
|
||||
}
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
|
||||
@@ -16,8 +16,6 @@ import com.wireguard.config.Attribute
|
||||
import com.wireguard.config.BadConfigException
|
||||
import com.wireguard.config.Peer
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.ArrayList
|
||||
import java.util.LinkedHashSet
|
||||
|
||||
class PeerProxy : BaseObservable, Parcelable {
|
||||
private val dnsRoutes: MutableList<String?> = ArrayList()
|
||||
@@ -240,24 +238,32 @@ class PeerProxy : BaseObservable, Parcelable {
|
||||
peerProxy.setTotalPeers(sender.size)
|
||||
}
|
||||
|
||||
override fun onItemRangeChanged(sender: ObservableList<PeerProxy?>,
|
||||
positionStart: Int, itemCount: Int) {
|
||||
override fun onItemRangeChanged(
|
||||
sender: ObservableList<PeerProxy?>,
|
||||
positionStart: Int, itemCount: Int
|
||||
) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
override fun onItemRangeInserted(sender: ObservableList<PeerProxy?>,
|
||||
positionStart: Int, itemCount: Int) {
|
||||
override fun onItemRangeInserted(
|
||||
sender: ObservableList<PeerProxy?>,
|
||||
positionStart: Int, itemCount: Int
|
||||
) {
|
||||
onChanged(sender)
|
||||
}
|
||||
|
||||
override fun onItemRangeMoved(sender: ObservableList<PeerProxy?>,
|
||||
fromPosition: Int, toPosition: Int,
|
||||
itemCount: Int) {
|
||||
override fun onItemRangeMoved(
|
||||
sender: ObservableList<PeerProxy?>,
|
||||
fromPosition: Int, toPosition: Int,
|
||||
itemCount: Int
|
||||
) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
override fun onItemRangeRemoved(sender: ObservableList<PeerProxy?>,
|
||||
positionStart: Int, itemCount: Int) {
|
||||
override fun onItemRangeRemoved(
|
||||
sender: ObservableList<PeerProxy?>,
|
||||
positionStart: Int, itemCount: Int
|
||||
) {
|
||||
onChanged(sender)
|
||||
}
|
||||
}
|
||||
@@ -276,12 +282,12 @@ class PeerProxy : BaseObservable, Parcelable {
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<PeerProxy> = PeerProxyCreator()
|
||||
private val IPV4_PUBLIC_NETWORKS = setOf(
|
||||
"0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4", "32.0.0.0/3",
|
||||
"64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6", "172.0.0.0/12",
|
||||
"172.32.0.0/11", "172.64.0.0/10", "172.128.0.0/9", "173.0.0.0/8", "174.0.0.0/7",
|
||||
"176.0.0.0/4", "192.0.0.0/9", "192.128.0.0/11", "192.160.0.0/13", "192.169.0.0/16",
|
||||
"192.170.0.0/15", "192.172.0.0/14", "192.176.0.0/12", "192.192.0.0/10",
|
||||
"193.0.0.0/8", "194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4"
|
||||
"0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4", "32.0.0.0/3",
|
||||
"64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6", "172.0.0.0/12",
|
||||
"172.32.0.0/11", "172.64.0.0/10", "172.128.0.0/9", "173.0.0.0/8", "174.0.0.0/7",
|
||||
"176.0.0.0/4", "192.0.0.0/9", "192.128.0.0/11", "192.160.0.0/13", "192.169.0.0/16",
|
||||
"192.170.0.0/15", "192.172.0.0/14", "192.176.0.0/12", "192.192.0.0/10",
|
||||
"193.0.0.0/8", "194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4"
|
||||
)
|
||||
private val IPV4_WILDCARD = setOf("0.0.0.0/0")
|
||||
}
|
||||
|
||||
@@ -13,10 +13,12 @@ import com.wireguard.crypto.Key
|
||||
* InputFilter for entering WireGuard private/public keys encoded with base64.
|
||||
*/
|
||||
class KeyInputFilter : InputFilter {
|
||||
override fun filter(source: CharSequence,
|
||||
sStart: Int, sEnd: Int,
|
||||
dest: Spanned,
|
||||
dStart: Int, dEnd: Int): CharSequence? {
|
||||
override fun filter(
|
||||
source: CharSequence,
|
||||
sStart: Int, sEnd: Int,
|
||||
dest: Spanned,
|
||||
dStart: Int, dEnd: Int
|
||||
): CharSequence? {
|
||||
var replacement: SpannableStringBuilder? = null
|
||||
var rIndex = 0
|
||||
val dLength = dest.length
|
||||
@@ -26,8 +28,9 @@ class KeyInputFilter : InputFilter {
|
||||
// Restrict characters to the base64 character set.
|
||||
// Ensure adding this character does not push the length over the limit.
|
||||
if ((dIndex + 1 < Key.Format.BASE64.length && isAllowed(c) ||
|
||||
dIndex + 1 == Key.Format.BASE64.length && c == '=') &&
|
||||
dLength + (sIndex - sStart) < Key.Format.BASE64.length) {
|
||||
dIndex + 1 == Key.Format.BASE64.length && c == '=') &&
|
||||
dLength + (sIndex - sStart) < Key.Format.BASE64.length
|
||||
) {
|
||||
++rIndex
|
||||
} else {
|
||||
if (replacement == null) replacement = SpannableStringBuilder(source, sStart, sEnd)
|
||||
|
||||
@@ -11,10 +11,10 @@ import android.widget.RelativeLayout
|
||||
import com.wireguard.android.R
|
||||
|
||||
class MultiselectableRelativeLayout @JvmOverloads constructor(
|
||||
context: Context? = null,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
defStyleRes: Int = 0
|
||||
context: Context? = null,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
defStyleRes: Int = 0
|
||||
) : RelativeLayout(context, attrs, defStyleAttr, defStyleRes) {
|
||||
private var multiselected = false
|
||||
|
||||
|
||||
@@ -13,10 +13,12 @@ import com.wireguard.android.backend.Tunnel
|
||||
* InputFilter for entering WireGuard configuration names (Linux interface names).
|
||||
*/
|
||||
class NameInputFilter : InputFilter {
|
||||
override fun filter(source: CharSequence,
|
||||
sStart: Int, sEnd: Int,
|
||||
dest: Spanned,
|
||||
dStart: Int, dEnd: Int): CharSequence? {
|
||||
override fun filter(
|
||||
source: CharSequence,
|
||||
sStart: Int, sEnd: Int,
|
||||
dest: Spanned,
|
||||
dStart: Int, dEnd: Int
|
||||
): CharSequence? {
|
||||
var replacement: SpannableStringBuilder? = null
|
||||
var rIndex = 0
|
||||
val dLength = dest.length
|
||||
@@ -26,7 +28,8 @@ class NameInputFilter : InputFilter {
|
||||
// Restrict characters to those valid in interfaces.
|
||||
// Ensure adding this character does not push the length over the limit.
|
||||
if (dIndex < Tunnel.NAME_MAX_LENGTH && isAllowed(c) &&
|
||||
dLength + (sIndex - sStart) < Tunnel.NAME_MAX_LENGTH) {
|
||||
dLength + (sIndex - sStart) < Tunnel.NAME_MAX_LENGTH
|
||||
) {
|
||||
++rIndex
|
||||
} else {
|
||||
if (replacement == null) replacement = SpannableStringBuilder(source, sStart, sEnd)
|
||||
|
||||
@@ -35,10 +35,10 @@ class SlashDrawable(private val mDrawable: Drawable) : Drawable() {
|
||||
val radiusX = scale(CORNER_RADIUS, width)
|
||||
val radiusY = scale(CORNER_RADIUS, height)
|
||||
updateRect(
|
||||
scale(LEFT, width),
|
||||
scale(TOP, height),
|
||||
scale(RIGHT, width),
|
||||
scale(TOP + mCurrentSlashLength, height)
|
||||
scale(LEFT, width),
|
||||
scale(TOP, height),
|
||||
scale(RIGHT, width),
|
||||
scale(TOP + mCurrentSlashLength, height)
|
||||
)
|
||||
mPath.reset()
|
||||
// Draw the slash vertically
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
app:state_multiselected="true">
|
||||
<color android:color="?attr/colorSurfaceVariant" />
|
||||
</item>
|
||||
<item
|
||||
android:state_activated="true">
|
||||
<item android:state_activated="true">
|
||||
<color android:color="?attr/colorControlHighlight" />
|
||||
</item>
|
||||
</selector>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<data>
|
||||
|
||||
<import type="com.wireguard.android.widget.NameInputFilter" />
|
||||
</data>
|
||||
|
||||
@@ -24,7 +25,8 @@
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="textNoSuggestions|textVisiblePassword"
|
||||
app:filter="@{NameInputFilter.newInstance()}">
|
||||
<requestFocus/>
|
||||
|
||||
<requestFocus />
|
||||
</com.google.android.material.textfield.TextInputEditText>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -60,11 +60,11 @@
|
||||
android:src="@mipmap/ic_launcher" />
|
||||
|
||||
<TextView
|
||||
android:layout_marginStart="@dimen/tunnel_list_placeholder_margin"
|
||||
android:layout_marginEnd="@dimen/tunnel_list_placeholder_margin"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="@dimen/tunnel_list_placeholder_margin"
|
||||
android:layout_marginEnd="@dimen/tunnel_list_placeholder_margin"
|
||||
android:text="@string/tunnel_list_placeholder"
|
||||
android:textSize="20sp" />
|
||||
</LinearLayout>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
unqualifiedResLocale=en-US
|
||||
@@ -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>
|
||||
|
||||
@@ -51,6 +51,9 @@
|
||||
<string name="add_peer">Tilføj modpart</string>
|
||||
<string name="addresses">Adresser</string>
|
||||
<string name="applications">Applikationer</string>
|
||||
<string name="allow_remote_control_intents_summary_off">Eksterne apps kan ikke slå tunneler til/fra (Anbefales)</string>
|
||||
<string name="allow_remote_control_intents_summary_on">Eksterne apps må slå tunneler til/fra (Avanceret)</string>
|
||||
<string name="allow_remote_control_intents_title">Tillad fjernstyring fra eksterne apps</string>
|
||||
<string name="allowed_ips">Tilladte IP-adresser</string>
|
||||
<string name="bad_config_context">%1$s\'s %2$s</string>
|
||||
<string name="bad_config_context_top_level">%s</string>
|
||||
@@ -72,6 +75,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>
|
||||
@@ -83,11 +88,13 @@
|
||||
<string name="tv_delete">Vælg tunnel du vil slette</string>
|
||||
<string name="tv_select_a_storage_drive">Vælg et lagerdrev</string>
|
||||
<string name="tv_add_tunnel_get_started">Tilføj en tunnel for at komme i gang</string>
|
||||
<string name="donate_title">♥ Donér til WireGuard projektet</string>
|
||||
<string name="disable_config_export_title">Deaktivér eksportering af konfiguration</string>
|
||||
<string name="dns_servers">DNS-servere</string>
|
||||
<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 +159,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>
|
||||
|
||||
@@ -119,6 +119,7 @@
|
||||
<string name="error_down">Fehler beim Abschalten des Tunnels: %s</string>
|
||||
<string name="error_fetching_apps">Fehler beim Abrufen der App-Liste: %s</string>
|
||||
<string name="error_root">Bitte root-Zugriff anfordern und erneut versuchen</string>
|
||||
<string name="error_prepare">Fehler beim Vorbereiten des Tunnels: %s</string>
|
||||
<string name="error_up">Fehler beim Starten des Tunnels: %s</string>
|
||||
<string name="exclude_private_ips">Private IPs ausschließen</string>
|
||||
<string name="generate_new_private_key">Neuen privaten Schlüssel generieren</string>
|
||||
@@ -138,6 +139,8 @@
|
||||
<string name="key_length_explanation_base64">: WireGuard base64-Schlüssel müssen 44 Zeichen enthalten (32 Bytes)</string>
|
||||
<string name="key_length_explanation_binary">: WireGuard-Schlüssel müssen 32 Bytes groß sein</string>
|
||||
<string name="key_length_explanation_hex">: WireGuard Hex-Schlüssel müssen 64 Zeichen (32 Bytes) groß sein</string>
|
||||
<string name="latest_handshake">Letzter Handshake</string>
|
||||
<string name="latest_handshake_ago">vor %s</string>
|
||||
<string name="listen_port">Eingangs-Port</string>
|
||||
<string name="log_export_error">Konnte Protokoll nicht exportieren: %s</string>
|
||||
<string name="log_export_subject">WireGuard Android Protokolldatei</string>
|
||||
@@ -181,6 +184,10 @@
|
||||
<string name="private_key">Privater Schlüssel</string>
|
||||
<string name="public_key">Öffentlicher Schlüssel</string>
|
||||
<string name="qr_code_hint">Tipp: Mit `qrencode -t ansiutf8 < tunnel.conf` generieren.</string>
|
||||
<string name="quick_settings_tile_add_title">Kachel zu Schnelleinstellungen hinzufügen</string>
|
||||
<string name="quick_settings_tile_add_summary">Die Verknüpfung schaltet den letzten Tunnel um</string>
|
||||
<string name="quick_settings_tile_add_failure">Verknüpfung kann nicht hinzugefügt werden: Fehler %d</string>
|
||||
<string name="quick_settings_tile_action">Tunnel umschalten</string>
|
||||
<string name="restore_on_boot_summary_off">Aktivierte Tunnel beim Systemstart nicht automatisch starten</string>
|
||||
<string name="restore_on_boot_summary_on">Aktivierte Tunnel beim Systemstart automatisch wieder starten</string>
|
||||
<string name="restore_on_boot_title">Beim Neustart wiederherstellen</string>
|
||||
@@ -216,6 +223,7 @@
|
||||
<string name="tunnel_create_success">Tunnel „%s “ erfolgreich erstellt</string>
|
||||
<string name="tunnel_error_already_exists">Tunnel „%s“ existiert bereits</string>
|
||||
<string name="tunnel_error_invalid_name">Ungültiger Name</string>
|
||||
<string name="tunnel_list_placeholder">Füge einen Tunnel mit der Schaltfläche unten hinzu</string>
|
||||
<string name="tunnel_name">Tunnelname</string>
|
||||
<string name="tunnel_on_error">Tunnel kann nicht eingeschaltet werden (wgTurnOn gab %d zurück)</string>
|
||||
<string name="tunnel_dns_failure">DNS-Hostname kann nicht aufgelöst werden: „%s“</string>
|
||||
@@ -224,6 +232,16 @@
|
||||
<string name="type_name_go_userspace">Go userspace</string>
|
||||
<string name="type_name_kernel_module">Kernelmodul</string>
|
||||
<string name="unknown_error">Unbekannter Fehler</string>
|
||||
<string name="updater_avalable">Ein Anwendungsupdate ist verfügbar. Bitte jetzt aktualisieren.</string>
|
||||
<string name="updater_action">Download & Update</string>
|
||||
<string name="updater_rechecking">Update-Metadaten abrufen…</string>
|
||||
<string name="updater_download_progress">Update wird heruntergeladen: %1$s / %2$s (%3$.2f%%)</string>
|
||||
<string name="updater_download_progress_nototal">Update wird heruntergeladen: %s</string>
|
||||
<string name="updater_installing">Installiere Update…</string>
|
||||
<string name="updater_failure">Fehler beim Aktualisieren: %s. Versuche es in Kürze erneut…</string>
|
||||
<string name="updater_corrupt_title">Anwendung beschädigt</string>
|
||||
<string name="updater_corrupt_message">Diese Anwendung ist beschädigt. Bitte laden Sie die APK erneut von der unten verlinkten Website herunter. Deinstallieren Sie danach diese Anwendung und installieren Sie sie mit der heruntergeladenen APK neu.</string>
|
||||
<string name="updater_corrupt_navigate">Webseite öffnen</string>
|
||||
<string name="version_summary">%1$s backend %2$s</string>
|
||||
<string name="version_summary_checking">Überprüfe %s Backend-Version</string>
|
||||
<string name="version_summary_unknown">Unbekannte %s Version</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>
|
||||
|
||||
@@ -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>
|
||||
@@ -178,15 +185,16 @@
|
||||
<string name="private_key">Privaatvõti</string>
|
||||
<string name="public_key">Avalik võti</string>
|
||||
<string name="qr_code_hint">Vihje: tekita käsuga `qrencode -t ansiutf8 < tunnel.conf`.</string>
|
||||
<string name="quick_settings_tile_action">Lülita tunnel</string>
|
||||
<string name="restore_on_boot_summary_off">Ei lülita seadme käivitumisel lubatud tunneleid sisse</string>
|
||||
<string name="restore_on_boot_summary_on">Lülitab seadme käivitumisel lubatud tunnelid sisse</string>
|
||||
<string name="restore_on_boot_title">Taasta seadme käivitumisel</string>
|
||||
<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 +221,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>
|
||||
@@ -221,6 +230,16 @@
|
||||
<string name="type_name_go_userspace">Go kasutajamaa</string>
|
||||
<string name="type_name_kernel_module">Tuumamoodul</string>
|
||||
<string name="unknown_error">Tundmatu viga</string>
|
||||
<string name="updater_avalable">Rakenduse uuendus on saadaval. Palun uuenda nüüd.</string>
|
||||
<string name="updater_action">Laadi alla ja uuenda</string>
|
||||
<string name="updater_rechecking">Uuenduse andmete laadimine…</string>
|
||||
<string name="updater_download_progress">Uuenduse allalaadimine: %1$s / %2$s (%3$.2f%%)</string>
|
||||
<string name="updater_download_progress_nototal">Uuenduse allalaadimine: %s</string>
|
||||
<string name="updater_installing">Uuenduse paigaldamine…</string>
|
||||
<string name="updater_failure">Uuendamine ebaõnnestus: %s. Uus katse hetke pärast…</string>
|
||||
<string name="updater_corrupt_title">Rakendus rikutud</string>
|
||||
<string name="updater_corrupt_message">See rakendus on rikutud. Palun laadi APK uuesti allpool lingitud veebilehelt. Pärast seda desinstalli rakendus ja installi allalaaditud APK uuesti.</string>
|
||||
<string name="updater_corrupt_navigate">Ava veebileht</string>
|
||||
<string name="version_summary">%1$s taustsüsteem %2$s</string>
|
||||
<string name="version_summary_checking">Kontrollin %s taustsüsteemi versiooni</string>
|
||||
<string name="version_summary_unknown">Tundmatu %s versioon</string>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</plurals>
|
||||
<plurals name="delete_success">
|
||||
<item quantity="one">Suppression réussie du tunnel %d</item>
|
||||
<item quantity="other">Supprimé avec succès %d tunnels</item>
|
||||
<item quantity="other">%d tunnels supprimés avec succès</item>
|
||||
</plurals>
|
||||
<plurals name="delete_title">
|
||||
<item quantity="one">%d tunnel sélectionné</item>
|
||||
@@ -85,16 +85,16 @@
|
||||
<string name="config_delete_error">Impossible de supprimer le fichier de configuration %s</string>
|
||||
<string name="config_exists_error">La configuration de « %s » existe déjà</string>
|
||||
<string name="config_file_exists_error">Le fichier de configuration « %s » existe déjà</string>
|
||||
<string name="config_not_found_error">Fichier de configuration «%s» introuvable</string>
|
||||
<string name="config_rename_error">Impossible de renommer le fichier de configuration «%s»</string>
|
||||
<string name="config_save_error">Impossible d’enregistrer la configuration pour «%1$s» : %2$s</string>
|
||||
<string name="config_save_success">Configuration enregistrée avec succès pour “%s”</string>
|
||||
<string name="config_not_found_error">Fichier de configuration « %s » introuvable</string>
|
||||
<string name="config_rename_error">Impossible de renommer le fichier de configuration « %s »</string>
|
||||
<string name="config_save_error">Impossible d’enregistrer la configuration pour « %1$s » : %2$s</string>
|
||||
<string name="config_save_success">Configuration enregistrée avec succès pour « %s »</string>
|
||||
<string name="create_activity_title">Créer un tunnel WireGuard</string>
|
||||
<string name="create_bin_dir_error">Impossible de créer le répertoire binaire local</string>
|
||||
<string name="create_downloads_file_error">Impossible de créer le fichier dans le répertoire des téléchargements</string>
|
||||
<string name="create_empty">Créer à partir de zéro</string>
|
||||
<string name="create_from_file">Importer depuis un fichier ou une archive</string>
|
||||
<string name="create_from_qr_code">Créer avec un scan de QR code</string>
|
||||
<string name="create_from_qr_code">Importer depuis un QR code</string>
|
||||
<string name="create_output_dir_error">Impossible de créer le répertoire de sortie</string>
|
||||
<string name="create_temp_dir_error">Impossible de créer le répertoire temporaire local</string>
|
||||
<string name="create_tunnel">Créer un tunnel</string>
|
||||
@@ -109,6 +109,7 @@
|
||||
<string name="tv_add_tunnel_get_started">Ajouter un tunnel pour commencer</string>
|
||||
<string name="donate_title">♥ Faire un don au projet WireGuard</string>
|
||||
<string name="donate_summary">Chaque contribution aide</string>
|
||||
<string name="donate_google_play_disappointment">Merci de votre soutien au projet WireGuard !\n\nMalheureusement, en raisons des politiques de Google, nous ne pouvons pas vous rediriger vers la page vous permettant de faire un don. Heureusement, vous pouvez le trouver par vous-même !\n\nMerci encore pour votre soutien.</string>
|
||||
<string name="disable_config_export_title">Désactiver l\'export de configuration</string>
|
||||
<string name="disable_config_export_description">La désactivation de l\'export de configuration rend les clés privées moins accessibles</string>
|
||||
<string name="dns_servers">Serveurs DNS</string>
|
||||
@@ -118,6 +119,7 @@
|
||||
<string name="error_down">Erreur lors de la désactivation du tunnel : %s</string>
|
||||
<string name="error_fetching_apps">Erreur lors de la récupération de la liste d\'applications : %s</string>
|
||||
<string name="error_root">Veuillez obtenir l\'accès root et essayez à nouveau</string>
|
||||
<string name="error_prepare">Erreur lors de la préparation du tunnel : %s</string>
|
||||
<string name="error_up">Erreur lors de la mise en place du tunnel : %s</string>
|
||||
<string name="exclude_private_ips">Exclure les IPs privées</string>
|
||||
<string name="generate_new_private_key">Générer une nouvelle clé privée</string>
|
||||
@@ -137,6 +139,8 @@
|
||||
<string name="key_length_explanation_base64">: Les clés base64 WireGuard doivent comporter 44 caractères (32 octets)</string>
|
||||
<string name="key_length_explanation_binary">: Les clés WireGuard doivent comporter 32 octets</string>
|
||||
<string name="key_length_explanation_hex">: Les clés hexadécimales WireGuard doivent comporter 64 caractères (32 octets)</string>
|
||||
<string name="latest_handshake">Dernière liaison</string>
|
||||
<string name="latest_handshake_ago">Il y a %s</string>
|
||||
<string name="listen_port">Port d\'écoute</string>
|
||||
<string name="log_export_error">Impossible d\'exporter le journal : %s</string>
|
||||
<string name="log_export_subject">Fichier journal d\'Android WireGuard</string>
|
||||
@@ -180,6 +184,10 @@
|
||||
<string name="private_key">Clé privée</string>
|
||||
<string name="public_key">Clé publique</string>
|
||||
<string name="qr_code_hint">Astuce : générez avec \"qrencode -t ansiutf8 < tunnel.conf\".</string>
|
||||
<string name="quick_settings_tile_add_title">Ajouter une bascule au volet des paramètres</string>
|
||||
<string name="quick_settings_tile_add_summary">Cette bascule active le dernier tunnel utilisé</string>
|
||||
<string name="quick_settings_tile_add_failure">Impossible d\'ajouter la bascule : erreur %d</string>
|
||||
<string name="quick_settings_tile_action">Activer le tunnel</string>
|
||||
<string name="restore_on_boot_summary_off">N\'affichera pas les tunnels activés au démarrage</string>
|
||||
<string name="restore_on_boot_summary_on">Les tunnels activés seront affichés au démarrage</string>
|
||||
<string name="restore_on_boot_title">Restaurer au démarrage</string>
|
||||
@@ -224,6 +232,16 @@
|
||||
<string name="type_name_go_userspace">Implémentation Go en espace utilisateur</string>
|
||||
<string name="type_name_kernel_module">Module noyau</string>
|
||||
<string name="unknown_error">Erreur inconnue</string>
|
||||
<string name="updater_avalable">Une mise à jour est disponible. Veuillez mettre l\'application à jour.</string>
|
||||
<string name="updater_action">Télécharger & Mettre à jour</string>
|
||||
<string name="updater_rechecking">Récupération des métadonnées de la mise à jour…</string>
|
||||
<string name="updater_download_progress">Téléchargement de la mise à jour : %1$s / %2$s (%3$.2f%%)</string>
|
||||
<string name="updater_download_progress_nototal">Téléchargement de la mise à jour : %s</string>
|
||||
<string name="updater_installing">Installation de la mise à jour…</string>
|
||||
<string name="updater_failure">Erreur lors de la mise à jour : %s. Nous réessaierons dans un instant…</string>
|
||||
<string name="updater_corrupt_title">Application corrompue</string>
|
||||
<string name="updater_corrupt_message">Cette application est corrompue. Veuillez réinstaller le fichier APK depuis le site ci-dessous. Ensuite, désinstallez cette application puis réinstallez-la à l\'aide du fichier APK téléchargé.</string>
|
||||
<string name="updater_corrupt_navigate">Accéder au site internet</string>
|
||||
<string name="version_summary">%1$s backend %2$s</string>
|
||||
<string name="version_summary_checking">Vérification de la version %s du backend</string>
|
||||
<string name="version_summary_unknown">Version %s inconnue</string>
|
||||
|
||||
@@ -59,9 +59,9 @@
|
||||
<string name="add_peer">Aggiungi peer</string>
|
||||
<string name="addresses">Indirizzi</string>
|
||||
<string name="applications">Applicazioni</string>
|
||||
<string name="allow_remote_control_intents_summary_off">Le applicazioni esterne non possono attivare tunnel (consigliato)</string>
|
||||
<string name="allow_remote_control_intents_summary_on">Le applicazioni esterne possono attivare tunnel (avanzato)</string>
|
||||
<string name="allow_remote_control_intents_title">Consenti applicazioni di controllo remoto</string>
|
||||
<string name="allow_remote_control_intents_summary_off">Le app esterne non possono attivare tunnel (consigliato)</string>
|
||||
<string name="allow_remote_control_intents_summary_on">Le app esterne possono attivare tunnel (avanzato)</string>
|
||||
<string name="allow_remote_control_intents_title">Consenti app di controllo remoto</string>
|
||||
<string name="allowed_ips">IP consentiti</string>
|
||||
<string name="bad_config_context">%2$s di %1$s</string>
|
||||
<string name="bad_config_context_top_level">%s</string>
|
||||
@@ -94,7 +94,7 @@
|
||||
<string name="create_downloads_file_error">Impossibile creare il file nella cartella di download</string>
|
||||
<string name="create_empty">Crea da zero</string>
|
||||
<string name="create_from_file">Importa da file o archivio</string>
|
||||
<string name="create_from_qr_code">Scansione da codice QR</string>
|
||||
<string name="create_from_qr_code">Scansiona da codice QR</string>
|
||||
<string name="create_output_dir_error">Impossibile creare la cartella di output</string>
|
||||
<string name="create_temp_dir_error">Impossibile creare la cartella locale temporanea</string>
|
||||
<string name="create_tunnel">Crea tunnel</string>
|
||||
@@ -107,6 +107,9 @@
|
||||
<string name="tv_select_a_storage_drive">Seleziona un\'unità di archiviazione</string>
|
||||
<string name="tv_no_file_picker">Installa un\'utilità di gestione file per sfogliare i file</string>
|
||||
<string name="tv_add_tunnel_get_started">Aggiungi un tunnel per iniziare</string>
|
||||
<string name="donate_title">♥ Dona al progetto WireGuard</string>
|
||||
<string name="donate_summary">Ogni contributo aiuta</string>
|
||||
<string name="donate_google_play_disappointment">Grazie per il sostegno al progetto WireGuard!\n\nPurtroppo, a causa delle politiche di Google, non siamo autorizzati a linkare la pagina del progetto dove puoi fare una donazione. Speriamo che la troverai!\n\nGrazie ancora per il tuo contributo.</string>
|
||||
<string name="disable_config_export_title">Disattiva esportazione config</string>
|
||||
<string name="disable_config_export_description">Disabilitare l\'esportazione della configurazione rende le chiavi private meno accessibili</string>
|
||||
<string name="dns_servers">Server DNS</string>
|
||||
@@ -116,13 +119,14 @@
|
||||
<string name="error_down">Errore di disattivazione del tunnel: %s</string>
|
||||
<string name="error_fetching_apps">Errore di recupero dell\'elenco applicazioni: %s</string>
|
||||
<string name="error_root">Accedi come root e riprova</string>
|
||||
<string name="error_prepare">Errore di preparazione del tunnel: %s</string>
|
||||
<string name="error_up">Errore di attivazione del tunnel: %s</string>
|
||||
<string name="exclude_private_ips">Escludi IP privati</string>
|
||||
<string name="generate_new_private_key">Genera nuova chiave privata</string>
|
||||
<string name="generic_error">Errore “%s” sconosciuto</string>
|
||||
<string name="hint_automatic">(auto)</string>
|
||||
<string name="hint_generated">(generato)</string>
|
||||
<string name="hint_optional">(facoltativo)</string>
|
||||
<string name="hint_generated">(generata)</string>
|
||||
<string name="hint_optional">(facoltativa)</string>
|
||||
<string name="hint_optional_discouraged">(facoltativo, non consigliato)</string>
|
||||
<string name="hint_random">(casuale)</string>
|
||||
<string name="illegal_filename_error">Nome file “%s” non valido</string>
|
||||
@@ -135,6 +139,8 @@
|
||||
<string name="key_length_explanation_base64">: le chiavi base64 di WireGuard devono essere di 44 caratteri (32 byte)</string>
|
||||
<string name="key_length_explanation_binary">: le chiavi di WireGuard devono essere di 32 byte</string>
|
||||
<string name="key_length_explanation_hex">: le chiavi esadecimali di WireGuard devono essere di 64 caratteri (32 byte)</string>
|
||||
<string name="latest_handshake">Ultima negoziazione</string>
|
||||
<string name="latest_handshake_ago">%s fa</string>
|
||||
<string name="listen_port">Porta in ascolto</string>
|
||||
<string name="log_export_error">Impossibile esportare il log: %s</string>
|
||||
<string name="log_export_subject">File di log WireGuard Android</string>
|
||||
@@ -178,6 +184,10 @@
|
||||
<string name="private_key">Chiave privata</string>
|
||||
<string name="public_key">Chiave pubblica</string>
|
||||
<string name="qr_code_hint">Suggerimento: genera con `qrencode -t ansiutf8 < tunnel.conf`.</string>
|
||||
<string name="quick_settings_tile_add_title">Aggiungi riquadro ale impostazioni rapide</string>
|
||||
<string name="quick_settings_tile_add_summary">La scorciatoia attiva/disattiva il tunnel più recente</string>
|
||||
<string name="quick_settings_tile_add_failure">Impossibile aggiungere la scorciatoia: errore %d</string>
|
||||
<string name="quick_settings_tile_action">Attiva/disattiva tunnel</string>
|
||||
<string name="restore_on_boot_summary_off">Non attiverà i tunnel configurati all\'avvio</string>
|
||||
<string name="restore_on_boot_summary_on">Attiverà i tunnel configurati all\'avvio</string>
|
||||
<string name="restore_on_boot_title">Ripristina all\'avvio</string>
|
||||
@@ -213,6 +223,7 @@
|
||||
<string name="tunnel_create_success">Tunnel “%s” creato correttamente</string>
|
||||
<string name="tunnel_error_already_exists">Il tunnel “%s” esiste già</string>
|
||||
<string name="tunnel_error_invalid_name">Nome non valido</string>
|
||||
<string name="tunnel_list_placeholder">Aggiungi un tunnel usando il pulsante sotto</string>
|
||||
<string name="tunnel_name">Nome tunnel</string>
|
||||
<string name="tunnel_on_error">Impossibile attivare il tunnel (wgTurnOn ha risposto %d)</string>
|
||||
<string name="tunnel_dns_failure">Impossibile risolve il nome di domino: \"%s\"</string>
|
||||
@@ -221,6 +232,16 @@
|
||||
<string name="type_name_go_userspace">Spazio utente Go</string>
|
||||
<string name="type_name_kernel_module">Modulo kernel</string>
|
||||
<string name="unknown_error">Errore sconosciuto</string>
|
||||
<string name="updater_avalable">È disponibile un aggiornamento dell\'app. Si prega di aggiornare ora.</string>
|
||||
<string name="updater_action">Scarica e aggiorna</string>
|
||||
<string name="updater_rechecking">Recupero metadati aggiornamento…</string>
|
||||
<string name="updater_download_progress">Scaricamento aggiornamento: %1$s / %2$s (%3$.2f%%)</string>
|
||||
<string name="updater_download_progress_nototal">Scaricamento aggiornamento: %s</string>
|
||||
<string name="updater_installing">Installazione aggiornamento…</string>
|
||||
<string name="updater_failure">Aggiornamento fallito: %s. Riprovo momentaneamente…</string>
|
||||
<string name="updater_corrupt_title">Applicazione danneggiata</string>
|
||||
<string name="updater_corrupt_message">Questa applicazione è danneggiata. Riscarica l\'APK dal sito collegato qui sotto. Dopo, disinstalla questa applicazione e reinstallala dall\'APK scaricato.</string>
|
||||
<string name="updater_corrupt_navigate">Apri sito web</string>
|
||||
<string name="version_summary">Backend %1$s %2$s</string>
|
||||
<string name="version_summary_checking">Controllo versione backend %s</string>
|
||||
<string name="version_summary_unknown">Versione %s sconosciuta</string>
|
||||
|
||||
@@ -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,10 @@
|
||||
<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="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>
|
||||
@@ -101,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">プライベート IP アドレスを除外</string>
|
||||
<string name="generate_new_private_key">新しい秘密鍵を生成する</string>
|
||||
@@ -120,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 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>
|
||||
@@ -163,6 +171,10 @@
|
||||
<string name="private_key">秘密鍵</string>
|
||||
<string name="public_key">公開鍵</string>
|
||||
<string name="qr_code_hint">Tip: `qrencode -t ansiutf8 < tunnel.conf` で生成できます</string>
|
||||
<string name="quick_settings_tile_add_title">クイック設定パネルを追加</string>
|
||||
<string name="quick_settings_tile_add_summary">ショートカットタイルを使用すると、最新のトンネルに切り替わります</string>
|
||||
<string name="quick_settings_tile_add_failure">ショートカットタイルを追加できません: エラー %d</string>
|
||||
<string name="quick_settings_tile_action">トンネルを切り替え</string>
|
||||
<string name="restore_on_boot_summary_off">起動時にトンネルを有効化しない</string>
|
||||
<string name="restore_on_boot_summary_on">起動時に、前回有効だったトンネルを有効化する</string>
|
||||
<string name="restore_on_boot_title">起動時に復元</string>
|
||||
@@ -198,6 +210,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>
|
||||
@@ -206,6 +219,16 @@
|
||||
<string name="type_name_go_userspace">Go ユーザースペース</string>
|
||||
<string name="type_name_kernel_module">カーネルモジュール</string>
|
||||
<string name="unknown_error">未知のエラー</string>
|
||||
<string name="updater_avalable">アプリを更新できます。今すぐ更新してください。</string>
|
||||
<string name="updater_action">ダウンロードして更新</string>
|
||||
<string name="updater_rechecking">更新のメタデータを取得しています…</string>
|
||||
<string name="updater_download_progress">更新のダウンロード中: %1$s / %2$s (%3$.2f%%)</string>
|
||||
<string name="updater_download_progress_nototal">更新のダウンロード中: %s</string>
|
||||
<string name="updater_installing">更新をインストール中…</string>
|
||||
<string name="updater_failure">更新に失敗しました: %s. 一定時間後に再試行します…</string>
|
||||
<string name="updater_corrupt_title">アプリケーションが破損しています</string>
|
||||
<string name="updater_corrupt_message">このアプリケーションは破損しています。下記のリンク先のウェブサイトから APK を再ダウンロードしてください。その後、このアプリケーションをアンインストールし、ダウンロードした APK を再インストールしてください。</string>
|
||||
<string name="updater_corrupt_navigate">ウェブサイトを開く</string>
|
||||
<string name="version_summary">%1$s バックエンド %2$s</string>
|
||||
<string name="version_summary_checking">%s バックエンドのバージョンを確認中</string>
|
||||
<string name="version_summary_unknown">未知の %s バージョン</string>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
<resources>
|
||||
|
||||
<style name="WireGuardTheme" parent="Theme.Material3.Dark">
|
||||
<item name="colorPrimary">@color/md_theme_dark_primary</item>
|
||||
<item name="colorOnPrimary">@color/md_theme_dark_onPrimary</item>
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
<item quantity="one">Kan %d tunnel niet verwijderen: %s</item>
|
||||
<item quantity="other">Kan %d tunnels niet verwijderen: %s</item>
|
||||
</plurals>
|
||||
<plurals name="delete_success">
|
||||
<item quantity="one">%d tunnel succesvol verwijderd</item>
|
||||
<item quantity="other">%d tunnels succesvol verwijderd</item>
|
||||
</plurals>
|
||||
<plurals name="delete_title">
|
||||
<item quantity="one">%d tunnel geselecteerd</item>
|
||||
<item quantity="other">%d tunnels geselecteerd</item>
|
||||
@@ -12,4 +16,193 @@
|
||||
<item quantity="one">%1$d van %2$d tunnels geïmporteerd</item>
|
||||
<item quantity="other">%1$d van de %2$d tunnels geïmporteerd</item>
|
||||
</plurals>
|
||||
<plurals name="import_total_success">
|
||||
<item quantity="one">%d tunnel geïmporteerd</item>
|
||||
<item quantity="other">%d tunnels geïmporteerd</item>
|
||||
</plurals>
|
||||
<plurals name="set_excluded_applications">
|
||||
<item quantity="one">%d uitgesloten applicatie(s)</item>
|
||||
<item quantity="other">%d uitgesloten applicaties</item>
|
||||
</plurals>
|
||||
<plurals name="n_excluded_applications">
|
||||
<item quantity="one">%d uitgesloten</item>
|
||||
<item quantity="other">%d uitgesloten</item>
|
||||
</plurals>
|
||||
<plurals name="n_included_applications">
|
||||
<item quantity="one">%d inbegrepen</item>
|
||||
<item quantity="other">%d inbegrepen</item>
|
||||
</plurals>
|
||||
<string name="all_applications">Alle applicaties</string>
|
||||
<string name="exclude_from_tunnel">Uitsluiten</string>
|
||||
<string name="include_in_tunnel">Alleen opnemen</string>
|
||||
<plurals name="include_n_applications">
|
||||
<item quantity="one">Neem %d app op</item>
|
||||
<item quantity="other">Voeg %d apps toe</item>
|
||||
</plurals>
|
||||
<plurals name="exclude_n_applications">
|
||||
<item quantity="one">%d app uitsluiten</item>
|
||||
<item quantity="other">%d apps uitsluiten</item>
|
||||
</plurals>
|
||||
<plurals name="persistent_keepalive_seconds_unit">
|
||||
<item quantity="one">iedere seconde</item>
|
||||
<item quantity="other">iedere %d seconden</item>
|
||||
</plurals>
|
||||
<string name="use_all_applications">Gebruik alle applicaties</string>
|
||||
<string name="add_peer">Peer toevoegen</string>
|
||||
<string name="addresses">Adressen</string>
|
||||
<string name="applications">Applicaties</string>
|
||||
<string name="allow_remote_control_intents_summary_off">Externe apps kunnen mogelijk geen tunnels in-/uitschakelen (aanbevolen)</string>
|
||||
<string name="allow_remote_control_intents_summary_on">Externe apps kunnen tunnels in-/uitschakelen (geavanceerd)</string>
|
||||
<string name="allow_remote_control_intents_title">Controle door externe besturingsapps toestaan</string>
|
||||
<string name="allowed_ips">Toegestane IP-adressen</string>
|
||||
<string name="bad_config_context">%1$s\'s %2$s</string>
|
||||
<string name="bad_config_context_top_level">%s</string>
|
||||
<string name="bad_config_error">%1$s in %2$s</string>
|
||||
<string name="bad_config_explanation_pka">: moet positief zijn en niet meer dan 65535</string>
|
||||
<string name="bad_config_explanation_positive_number">: Moet positief zijn</string>
|
||||
<string name="bad_config_explanation_udp_port">: Moet een geldig UDP poortnummer zijn</string>
|
||||
<string name="bad_config_reason_invalid_key">Ongeldige sleutel</string>
|
||||
<string name="bad_config_reason_invalid_number">Ongeldig nummer</string>
|
||||
<string name="bad_config_reason_invalid_value">Ongeldige waarde</string>
|
||||
<string name="bad_config_reason_missing_attribute">Attribuut ontbreekt</string>
|
||||
<string name="bad_config_reason_missing_section">Ontbrekende sectie</string>
|
||||
<string name="bad_config_reason_syntax_error">Syntaxfout</string>
|
||||
<string name="bad_config_reason_unknown_attribute">Onbekend attribuut</string>
|
||||
<string name="bad_config_reason_unknown_section">Onbekende sectie</string>
|
||||
<string name="bad_config_reason_value_out_of_range">Waarde buiten bereik</string>
|
||||
<string name="bad_extension_error">Bestand moet .conf of .zip zijn</string>
|
||||
<string name="error_no_qr_found">QR-code niet gevonden in afbeelding</string>
|
||||
<string name="error_qr_checksum">QR-code checksum verificatie mislukt</string>
|
||||
<string name="cancel">Annuleren</string>
|
||||
<string name="config_delete_error">Kan configuratiebestand %s niet verwijderen</string>
|
||||
<string name="config_exists_error">Configuratie voor \"%s\" bestaat al</string>
|
||||
<string name="config_file_exists_error">Configuratiebestand \"%s\" bestaat al</string>
|
||||
<string name="config_not_found_error">Configuratiebestand \"%s\" niet gevonden</string>
|
||||
<string name="config_rename_error">Kan configuratiebestand \"%s\" \" niet hernoemen</string>
|
||||
<string name="config_save_error">Kan de configuratie voor \"%1$s\" niet opslaan: %2$s</string>
|
||||
<string name="config_save_success">Configuratie succesvol opgeslagen voor \"%s\"</string>
|
||||
<string name="create_activity_title">WireGuard tunnel aanmaken</string>
|
||||
<string name="create_bin_dir_error">Kan geen lokale \'bin\' map aanmaken</string>
|
||||
<string name="create_downloads_file_error">Kan bestand niet maken in downloadmap</string>
|
||||
<string name="create_empty">Begin met lege configuratie</string>
|
||||
<string name="create_from_file">Importeren uit bestand of archief</string>
|
||||
<string name="create_from_qr_code">Scan van QR code</string>
|
||||
<string name="create_output_dir_error">Kan de output map niet aanmaken</string>
|
||||
<string name="create_temp_dir_error">Kan geen tijdelijke map aanmaken</string>
|
||||
<string name="create_tunnel">Maak nieuwe tunnel</string>
|
||||
<string name="copied_to_clipboard">%s gekopieerd naar klembord</string>
|
||||
<string name="dark_theme_summary_off">Momenteel wordt licht (dag) thema gebruikt</string>
|
||||
<string name="dark_theme_summary_on">Momenteel wordt donker (nacht) thema gebruikt</string>
|
||||
<string name="dark_theme_title">Gebruik donker thema</string>
|
||||
<string name="delete">Verwijder</string>
|
||||
<string name="tv_delete">Selecteer tunnel om te verwijderen</string>
|
||||
<string name="tv_select_a_storage_drive">Selecteer een opslaglocatie</string>
|
||||
<string name="tv_no_file_picker">Installeer een bestandsbeheer applicatie</string>
|
||||
<string name="tv_add_tunnel_get_started">Voeg een tunnel toe om te beginnen</string>
|
||||
<string name="donate_title">♥️ Doneer aan het WireGuard Project</string>
|
||||
<string name="donate_summary">Elke bijdrage helpt</string>
|
||||
<string name="donate_google_play_disappointment">Bedankt voor het steunen van het WireGuard Project!\n\nHelaas, als gevolg van Google beleid, We mogen niet linken naar de webpagina van het project waar u een donatie kunt doen. Hopelijk kunt u deze zelf wel vinden!\n\nNogmaals bedankt voor uw bijdrage.</string>
|
||||
<string name="disable_config_export_title">Config export uitschakelen</string>
|
||||
<string name="disable_config_export_description">Het uitschakelen van configuratie export maakt privésleutels minder toegankelijk</string>
|
||||
<string name="dns_servers">DNS-servers</string>
|
||||
<string name="dns_search_domains">DNS-zoekdomeinen</string>
|
||||
<string name="edit">Bewerken</string>
|
||||
<string name="endpoint">Eindpunt</string>
|
||||
<string name="error_down">Fout bij stoppen tunnel: %s</string>
|
||||
<string name="error_fetching_apps">Fout bij ophalen van apps-lijst: %s</string>
|
||||
<string name="error_root">Verkrijg root toegang en probeer het opnieuw</string>
|
||||
<string name="error_prepare">Fout bij voorbereiden tunnel: %s</string>
|
||||
<string name="error_up">Fout bij het starten van tunnel: %s</string>
|
||||
<string name="exclude_private_ips">Privé-IP\'s uitsluiten</string>
|
||||
<string name="generate_new_private_key">Nieuwe privésleutel genereren</string>
|
||||
<string name="generic_error">Onbekend fout: \"%s\"</string>
|
||||
<string name="hint_automatic">(auto)</string>
|
||||
<string name="hint_generated">(gegenereerd)</string>
|
||||
<string name="hint_optional">(optioneel)</string>
|
||||
<string name="hint_optional_discouraged">(optioneel, niet aanbevolen)</string>
|
||||
<string name="hint_random">(willekeurig)</string>
|
||||
<string name="illegal_filename_error">Ongeldige bestandsnaam \"%s\" \"</string>
|
||||
<string name="import_error">Kan tunnel niet importeren: %s</string>
|
||||
<string name="import_from_qr_code">Importeer Tunnel uit QR Code</string>
|
||||
<string name="import_success">Geïmporteerd \"%s\"</string>
|
||||
<string name="interface_title">Interface</string>
|
||||
<string name="key_contents_error">Slechte tekens in de veld</string>
|
||||
<string name="key_length_error">Onjuiste sleutellengte</string>
|
||||
<string name="key_length_explanation_base64">: WireGuard base64 sleutels moeten 44 tekens zijn (32 bytes)</string>
|
||||
<string name="key_length_explanation_binary">: WireGuard sleutels moeten 32 bytes zijn</string>
|
||||
<string name="key_length_explanation_hex">: WireGuard hex sleutels moeten 64 tekens zijn (32 bytes)</string>
|
||||
<string name="latest_handshake">Recentste uitwisseling</string>
|
||||
<string name="latest_handshake_ago">%s geleden</string>
|
||||
<string name="listen_port">Luister op poort</string>
|
||||
<string name="log_export_error">Kan logboek niet exporteren: %s</string>
|
||||
<string name="log_export_subject">WireGuard Android logbestand</string>
|
||||
<string name="log_export_success">Opgeslagen in \"%s\"</string>
|
||||
<string name="log_export_title">Exporteer logboek naar bestand</string>
|
||||
<string name="log_saver_activity_label">Logboek opslaan</string>
|
||||
<string name="log_viewer_pref_summary">Logboeken kunnen helpen bij het debuggen</string>
|
||||
<string name="log_viewer_pref_title">Bekijk applicatielogboek</string>
|
||||
<string name="log_viewer_title">Log</string>
|
||||
<string name="logcat_error">Kan logcat niet uitvoeren: </string>
|
||||
<string name="module_enabler_disabled_summary">De experimentele kernel module kan de prestaties verbeteren</string>
|
||||
<string name="module_enabler_disabled_title">Kernel module backend inschakelen</string>
|
||||
<string name="module_enabler_enabled_summary">De langzamere userspace backend kan de stabiliteit verbeteren</string>
|
||||
<string name="module_enabler_enabled_title">Uitschakelen kernel module backend</string>
|
||||
<string name="module_installer_error">Er ging iets mis. Probeer het nog eens</string>
|
||||
<string name="module_installer_initial">De experimentele kernel module kan de prestaties verbeteren</string>
|
||||
<string name="module_installer_not_found">Er zijn geen modules beschikbaar voor uw apparaat</string>
|
||||
<string name="module_installer_title">Download en installeer kernel module</string>
|
||||
<string name="module_installer_working">Downloaden en installeren…</string>
|
||||
<string name="module_version_error">Niet in staat om kernel module versie te bepalen</string>
|
||||
<string name="mtu">MTU</string>
|
||||
<string name="multiple_tunnels_summary_off">Het inschakelen van één tunnel zal anderen uitzetten</string>
|
||||
<string name="multiple_tunnels_summary_on">Meerdere tunnels kunnen tegelijkertijd actief zijn</string>
|
||||
<string name="multiple_tunnels_title">Meerdere gelijktijdige tunnels toestaan</string>
|
||||
<string name="name">Naam</string>
|
||||
<string name="no_config_error">Probeer een tunnel zonder configuratie te starten</string>
|
||||
<string name="no_configs_error">Geen configuraties gevonden</string>
|
||||
<string name="no_tunnels_error">Geen tunnels gedefinieerd</string>
|
||||
<string name="parse_error_generic">string</string>
|
||||
<string name="parse_error_inet_address">IP-adres</string>
|
||||
<string name="parse_error_inet_endpoint">eindpunt</string>
|
||||
<string name="parse_error_inet_network">IP netwerk</string>
|
||||
<string name="parse_error_integer">nummer</string>
|
||||
<string name="parse_error_reason">Kan %1$s%2$s niet parsen</string>
|
||||
<string name="peer">Peer</string>
|
||||
<string name="permission_label">WireGuard tunnels beheren</string>
|
||||
<string name="persistent_keepalive">Voortdurende verbindingstest</string>
|
||||
<string name="pre_shared_key">Gedeelde sleutel</string>
|
||||
<string name="pre_shared_key_enabled">ingeschakeld</string>
|
||||
<string name="private_key">Privésleutel</string>
|
||||
<string name="public_key">Publieke sleutel</string>
|
||||
<string name="quick_settings_tile_action">tunnel in-/uitschakelen</string>
|
||||
<string name="restore_on_boot_title">Tunnel starten bij herstart</string>
|
||||
<string name="save">Opslaan</string>
|
||||
<string name="select_all">Selecteer alles</string>
|
||||
<string name="settings">Instellingen</string>
|
||||
<string name="toggle_all">Alles wisselen</string>
|
||||
<string name="tools_installer_already">wg and wg-quick zijn al geïnstalleerd</string>
|
||||
<string name="tools_installer_initial">Optionele tools voor scripts installeren</string>
|
||||
<string name="tools_installer_title">Installeer command line tools</string>
|
||||
<string name="tools_installer_working">Installeren van wg en wg-quick</string>
|
||||
<string name="transfer_bytes">%d B</string>
|
||||
<string name="transfer_gibibytes">%.2f GiB</string>
|
||||
<string name="transfer_kibibytes">%.2f KiB</string>
|
||||
<string name="transfer_mibibytes">%.2f MiB</string>
|
||||
<string name="transfer_tibibytes">%.2f TiB</string>
|
||||
<string name="tun_create_error">Kan tun apparaat niet aanmaken</string>
|
||||
<string name="tunnel_error_already_exists">Tunnel \"%s\" bestaat al</string>
|
||||
<string name="tunnel_error_invalid_name">Ongeldige naam</string>
|
||||
<string name="tunnel_list_placeholder">Voeg een tunnel toe met de knop hieronder</string>
|
||||
<string name="tunnel_name">Tunnelnaam</string>
|
||||
<string name="type_name_go_userspace">Go userspace</string>
|
||||
<string name="type_name_kernel_module">Kernel module</string>
|
||||
<string name="unknown_error">Onbekende fout</string>
|
||||
<string name="updater_action">Download & installeer updates</string>
|
||||
<string name="updater_rechecking">Update metadata downloaden…</string>
|
||||
<string name="updater_download_progress_nototal">Updates downloaden: %s</string>
|
||||
<string name="updater_installing">Update wordt geïnstalleerd…</string>
|
||||
<string name="biometric_prompt_zip_exporter_title">Authenticeer om de tunnel configuratie te exporteren</string>
|
||||
<string name="biometric_prompt_private_key_title">Authenticeer om de persoonlijke sleutel te bekijken</string>
|
||||
<string name="biometric_auth_error">Authenticatiefout</string>
|
||||
<string name="biometric_auth_error_reason">Authenticatiefout: %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
<string name="tv_select_a_storage_drive">Selecione uma unidade de armazenamento</string>
|
||||
<string name="tv_no_file_picker">Por favor, instale um utilitário de gerenciamento de arquivos para procurar arquivos</string>
|
||||
<string name="tv_add_tunnel_get_started">Adicione um túnel para começar</string>
|
||||
<string name="donate_title">♥️ Doar para o projeto WireGuard</string>
|
||||
<string name="disable_config_export_title">Desativar exportação de configuração</string>
|
||||
<string name="disable_config_export_description">Desativar a exportação de configuração torna as chaves privadas menos acessíveis</string>
|
||||
<string name="dns_servers">Servidores DNS</string>
|
||||
|
||||
@@ -120,6 +120,9 @@
|
||||
<string name="tv_select_a_storage_drive">Selectează o unitate de stocare</string>
|
||||
<string name="tv_no_file_picker">Instalează un serviciu de administrare a fișierelor pentru a căuta fișiere</string>
|
||||
<string name="tv_add_tunnel_get_started">Adaugă un tunel pentru a începe</string>
|
||||
<string name="donate_title">♥ Donează pentru proiectul WireGuard</string>
|
||||
<string name="donate_summary">Fiecare contribuţie ajută</string>
|
||||
<string name="donate_google_play_disappointment">Vă mulțumim pentru sprijinul acordat Proiectului WireGuard!\n\nDin păcate, din cauza politicilor Google, nu avem voie să punem un link către pagina web a proiectului unde poți face o donație. Sperăm că vă puteți descurca!\n\nMulțumim din nou pentru contribuție.</string>
|
||||
<string name="disable_config_export_title">Dezactivează exportarea configurației</string>
|
||||
<string name="disable_config_export_description">Dezactivarea exportării configurației face mai puțin accesibile cheile private</string>
|
||||
<string name="dns_servers">Servere DNS</string>
|
||||
@@ -129,6 +132,7 @@
|
||||
<string name="error_down">Eroare la oprirea tunelului: %s</string>
|
||||
<string name="error_fetching_apps">Eroare la preluarea listei de aplicații: %s</string>
|
||||
<string name="error_root">Obține acces root și încearcă din nou</string>
|
||||
<string name="error_prepare">Eroare la pregătirea tunelului: %s</string>
|
||||
<string name="error_up">Eroare la pornirea tunelului: %s</string>
|
||||
<string name="exclude_private_ips">Excludere IP-uri private</string>
|
||||
<string name="generate_new_private_key">Generare cheie privată nouă</string>
|
||||
@@ -148,6 +152,8 @@
|
||||
<string name="key_length_explanation_base64">: Cheile base64 ale WireGuard trebuie să aibă 44 de caractere (32 de octeți)</string>
|
||||
<string name="key_length_explanation_binary">: Cheile WireGuard trebuie să aibă 32 de octeți</string>
|
||||
<string name="key_length_explanation_hex">: Cheile hex WireGuard trebuie să aibă 64 de caractere (32 de octeți)</string>
|
||||
<string name="latest_handshake">Cea mai recentă negociere</string>
|
||||
<string name="latest_handshake_ago">%s în urmă</string>
|
||||
<string name="listen_port">Port de ascultare</string>
|
||||
<string name="log_export_error">Jurnalul nu poate fi exportat: %s</string>
|
||||
<string name="log_export_subject">Fișier de jurnal Android WireGuard</string>
|
||||
@@ -191,6 +197,8 @@
|
||||
<string name="private_key">Cheie privată</string>
|
||||
<string name="public_key">Cheie publică</string>
|
||||
<string name="qr_code_hint">Sfat: generează cu `qrencode -t ansiutf8 < tunnel.conf`.</string>
|
||||
<string name="quick_settings_tile_add_title">Adaugă secțiune la panoul de setări rapide</string>
|
||||
<string name="quick_settings_tile_add_summary">Comanda rapidă comută cel mai recent tunel</string>
|
||||
<string name="restore_on_boot_summary_off">Tunelurile activate nu vor fi pornite odată cu pornirea dispozitivului</string>
|
||||
<string name="restore_on_boot_summary_on">Tunelurile activate vor fi pornite odată cu pornirea dispozitivului</string>
|
||||
<string name="restore_on_boot_title">Restaurare la pornire</string>
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
<string name="bad_config_error">%1$s в %2$s</string>
|
||||
<string name="bad_config_explanation_pka">: Значение должно быть больше нуля, но меньше 65535</string>
|
||||
<string name="bad_config_explanation_positive_number">: Значение должно быть больше нуля</string>
|
||||
<string name="bad_config_explanation_udp_port">: Должен быть действительный номер порта UDP</string>
|
||||
<string name="bad_config_explanation_udp_port">: Должен быть допустимым UDP-портом</string>
|
||||
<string name="bad_config_reason_invalid_key">Неправильный ключ</string>
|
||||
<string name="bad_config_reason_invalid_number">Неправильный номер</string>
|
||||
<string name="bad_config_reason_invalid_value">Недопустимое значение</string>
|
||||
@@ -133,15 +133,19 @@
|
||||
<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>
|
||||
<string name="dns_search_domains">Домены поиска</string>
|
||||
<string name="edit">Изменить</string>
|
||||
<string name="endpoint">Конечная точка</string>
|
||||
<string name="error_down">Ошибка при выходе из туннеля: %s</string>
|
||||
<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>
|
||||
@@ -196,7 +202,7 @@
|
||||
<string name="parse_error_integer">число</string>
|
||||
<string name="parse_error_reason">Невозможно разобрать %1$s “%2$s”</string>
|
||||
<string name="peer">Пир</string>
|
||||
<string name="permission_description">управлять туннелями WireGuard, включая и отключая туннели по желанию, потенциально перенаправляя интернет-трафик</string>
|
||||
<string name="permission_description">контроль над туннелями WireGuard, включение и отключение туннелей по своему усмотрению, возможность неправильного управления сетевым трафиком</string>
|
||||
<string name="permission_label">управлять туннелями WireGuard</string>
|
||||
<string name="persistent_keepalive">Постоянное соединение</string>
|
||||
<string name="pre_shared_key">Общий ключ</string>
|
||||
@@ -204,6 +210,10 @@
|
||||
<string name="private_key">Приватный ключ</string>
|
||||
<string name="public_key">Публичный ключ</string>
|
||||
<string name="qr_code_hint">Совет: генерировать с “qrencode -t ansiutf8 < tunnel.conf”.</string>
|
||||
<string name="quick_settings_tile_add_title">Добавить элемент в панель быстрых настроек</string>
|
||||
<string name="quick_settings_tile_add_summary">Элемент переключает последний активный туннель</string>
|
||||
<string name="quick_settings_tile_add_failure">Не удается добавить ярлык: ошибка %d</string>
|
||||
<string name="quick_settings_tile_action">Переключить туннель</string>
|
||||
<string name="restore_on_boot_summary_off">Не поднимать ранее выбранные туннели при загрузке</string>
|
||||
<string name="restore_on_boot_summary_on">Поднимать ранее выбранные туннели при загрузке</string>
|
||||
<string name="restore_on_boot_title">Восстановить при загрузке</string>
|
||||
@@ -239,15 +249,26 @@
|
||||
<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>
|
||||
<string name="tunnel_rename_error">Не удалось переименовать туннель: %s</string>
|
||||
<string name="tunnel_rename_success">Туннель успешно переименован в “%s”</string>
|
||||
<string name="type_name_go_userspace">Пользовательское пространство Go</string>
|
||||
<string name="type_name_go_userspace">Go в пользовательском пространстве</string>
|
||||
<string name="type_name_kernel_module">Модуль ядра</string>
|
||||
<string name="unknown_error">Неизвестная ошибка</string>
|
||||
<string name="version_summary">%1$s бэкенд %2$s</string>
|
||||
<string name="updater_avalable">Доступно обновление приложения. Пожалуйста, обновите.</string>
|
||||
<string name="updater_action">Загрузить и установить</string>
|
||||
<string name="updater_rechecking">Получение метаданных обновления…</string>
|
||||
<string name="updater_download_progress">Загрузка обновления: %1$s / %2$s (%3$.2f%%)</string>
|
||||
<string name="updater_download_progress_nototal">Загрузка обновления: %s</string>
|
||||
<string name="updater_installing">Установка обновления…</string>
|
||||
<string name="updater_failure">Ошибка обновления: %s. Повторите попытку…</string>
|
||||
<string name="updater_corrupt_title">Приложение повреждено</string>
|
||||
<string name="updater_corrupt_message">Приложение повреждено. Загрузите APK с сайта, указанного ниже, затем удалите это приложение и установите из загруженного APK.</string>
|
||||
<string name="updater_corrupt_navigate">Открыть сайт</string>
|
||||
<string name="version_summary">Бэкенд: %1$s %2$s</string>
|
||||
<string name="version_summary_checking">Проверка версии бэкэнда %s</string>
|
||||
<string name="version_summary_unknown">Неизвестная версия %s</string>
|
||||
<string name="version_title">WireGuard для Android v%s</string>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<string name="addresses">Adresy</string>
|
||||
<string name="applications">Aplikácie</string>
|
||||
<string name="allow_remote_control_intents_summary_off">Externé aplikácie nemôžu spustiť tunely (odporúčané)</string>
|
||||
<string name="allow_remote_control_intents_summary_on">Externé aplikácie môžu spustiť tunely (odporúčané)</string>
|
||||
<string name="allow_remote_control_intents_summary_on">Externé aplikácie môžu spustiť tunely (pokročilé)</string>
|
||||
<string name="allow_remote_control_intents_title">Povoliť aplikáciám vzdialenú správu</string>
|
||||
<string name="allowed_ips">Povolené IP adresy</string>
|
||||
<string name="bad_config_context_top_level">%s</string>
|
||||
@@ -20,8 +20,8 @@
|
||||
<string name="bad_config_reason_invalid_number">Neplatné číslo</string>
|
||||
<string name="bad_config_reason_invalid_value">Neplatná hodnota</string>
|
||||
<string name="bad_config_reason_missing_attribute">Chýbajúci atribút</string>
|
||||
<string name="bad_config_reason_missing_section">Chýbajúc sekcia</string>
|
||||
<string name="bad_config_reason_syntax_error">Chyba syntaxu</string>
|
||||
<string name="bad_config_reason_missing_section">Chýbajúca sekcia</string>
|
||||
<string name="bad_config_reason_syntax_error">Chyba syntaxe</string>
|
||||
<string name="bad_config_reason_unknown_attribute">Neznámy atribút</string>
|
||||
<string name="bad_config_reason_unknown_section">Neznáma sekcia</string>
|
||||
<string name="bad_config_reason_value_out_of_range">Hodnota mimo povoleného rozsahu</string>
|
||||
@@ -31,12 +31,12 @@
|
||||
<string name="config_exists_error">Konfigurácia pre “%s” už existuje</string>
|
||||
<string name="config_file_exists_error">Konfiguračný súbor pre “%s” už existuje</string>
|
||||
<string name="config_not_found_error">Konfiguračný súbor “%s” sa nenašiel</string>
|
||||
<string name="config_rename_error">Nemôžete premenovať konfiguračný súbor “%s”</string>
|
||||
<string name="config_save_error">Nemôžete uložiť konfiguráciu pre “%1$s”: %2$s</string>
|
||||
<string name="config_rename_error">Nepodarilo sa premenovať konfiguračný súbor “%s”</string>
|
||||
<string name="config_save_error">Nepodarilo sa uložiť konfiguráciu pre “%1$s”: %2$s</string>
|
||||
<string name="config_save_success">Úspešne sa podarilo uložiť konfiguráciu pre “%s”</string>
|
||||
<string name="create_activity_title">Vytvoriť WireGuard tunel</string>
|
||||
<string name="create_bin_dir_error">Nemôžete vytvoriť lokálny binárny súbor</string>
|
||||
<string name="create_downloads_file_error">Nemôžete vytvoriť súbor v priečinku downloads</string>
|
||||
<string name="create_bin_dir_error">Nepodarilo sa vytvoriť lokálny priečinok pre binárne súbory</string>
|
||||
<string name="create_downloads_file_error">Nepodarilo sa vytvoriť súbor v priečinku stiahnuté</string>
|
||||
<string name="create_empty">Vytvoriť od počiatku</string>
|
||||
<string name="create_from_file">Importovať zo súboru alebo archívu</string>
|
||||
<string name="create_from_qr_code">Skenovať z QR kódu</string>
|
||||
@@ -50,10 +50,10 @@
|
||||
<string name="delete">Odstrániť</string>
|
||||
<string name="tv_delete">Vyberte tunel na odstránenie</string>
|
||||
<string name="tv_select_a_storage_drive">Vyberte úložnú jednotku</string>
|
||||
<string name="tv_no_file_picker">Prosím nainštalujte manažéra súbor aby ste mohli prehliadať súbory</string>
|
||||
<string name="tv_no_file_picker">Prosím nainštalujte manažéra súborov aby ste mohli prehliadať súbory</string>
|
||||
<string name="tv_add_tunnel_get_started">Pridajte tunel aby ste mohli začať</string>
|
||||
<string name="disable_config_export_title">Zakázať export konfigurácie</string>
|
||||
<string name="disable_config_export_description">Zakázanie exportu konfigurácii spôsobí, že prístup k súkromným kľúčom sa stáva zložitým</string>
|
||||
<string name="disable_config_export_description">Zakázanie exportu konfigurácie spôsobí, že prístup k súkromným kľúčom sa stáva zložitým</string>
|
||||
<string name="dns_servers">Servery DNS</string>
|
||||
<string name="dns_search_domains">Prehľadávať domény</string>
|
||||
<string name="edit">Upraviť</string>
|
||||
@@ -61,46 +61,46 @@
|
||||
<string name="error_down">Chyba pri vypínaní tunela: %s</string>
|
||||
<string name="error_fetching_apps">Chyba pri načítaní zoznamu aplikácií: %s</string>
|
||||
<string name="error_root">Získajte prístup root a skúste znova</string>
|
||||
<string name="error_up">Chyba pri vyvolávaní tunela: %s</string>
|
||||
<string name="exclude_private_ips">Vynechať privátne IP</string>
|
||||
<string name="generate_new_private_key">Generovať nový privátny kľúč</string>
|
||||
<string name="error_up">Chyba pri zapínaní tunela: %s</string>
|
||||
<string name="exclude_private_ips">Vynechať súkromné IP</string>
|
||||
<string name="generate_new_private_key">Generovať nový súkromný kľúč</string>
|
||||
<string name="generic_error">Neznáma “%s” chyba</string>
|
||||
<string name="hint_automatic">(automatické)</string>
|
||||
<string name="hint_generated">(generované)</string>
|
||||
<string name="hint_optional">(voliteľné)</string>
|
||||
<string name="hint_optional_discouraged">(voliteľné, neodporúča sa)</string>
|
||||
<string name="hint_random">(náhodné)</string>
|
||||
<string name="illegal_filename_error">Ilegálne meno súboru “%s”</string>
|
||||
<string name="illegal_filename_error">Nepovolené meno súboru “%s”</string>
|
||||
<string name="import_error">Nepodarilo sa importovať tunel: %s</string>
|
||||
<string name="import_from_qr_code">Importovať tunel z QR kódu</string>
|
||||
<string name="import_success">Podarilo sa importovať “%s”</string>
|
||||
<string name="interface_title">Rozhranie</string>
|
||||
<string name="key_contents_error">Nepovolené charaktery v kľúči</string>
|
||||
<string name="key_contents_error">Nepovolené znaky v kľúči</string>
|
||||
<string name="key_length_error">Nesprávna dĺžka kľúču</string>
|
||||
<string name="key_length_explanation_base64">: WireGuard base64 kľúče musia mať 44 charakterov (32 bytes)</string>
|
||||
<string name="key_length_explanation_base64">: WireGuard base64 kľúče musia mať 44 znakov (32 bytes)</string>
|
||||
<string name="key_length_explanation_binary">: WireGuard kľúče musia byť 32 bytové</string>
|
||||
<string name="key_length_explanation_hex">: WireGuard hex kľúče musia mať 64 charakterov (32 bytes)</string>
|
||||
<string name="key_length_explanation_hex">: WireGuard hex kľúče musia mať 64 znakov (32 bytes)</string>
|
||||
<string name="listen_port">Otvorený port</string>
|
||||
<string name="log_export_error">Nepodarilo sa exportovať log: %s</string>
|
||||
<string name="log_export_subject">WireGuard Android Záznam</string>
|
||||
<string name="log_export_subject">WireGuard Android Denník udalostí</string>
|
||||
<string name="log_export_success">Uložené do “%s”</string>
|
||||
<string name="log_export_title">Exportovať denník udalostí</string>
|
||||
<string name="log_saver_activity_label">Uložiť denník udalostí</string>
|
||||
<string name="log_viewer_pref_summary">Denník udalosti môžu byt nápomocné pri ladení aplikácie</string>
|
||||
<string name="log_viewer_pref_summary">Denníky udalostí môžu byt nápomocné pri ladení aplikácie</string>
|
||||
<string name="log_viewer_pref_title">Zobraziť denník udalostí aplikácie</string>
|
||||
<string name="log_viewer_title">Denník udalostí</string>
|
||||
<string name="logcat_error">Nepodarilo sa prehrať logcat: </string>
|
||||
<string name="module_enabler_enabled_summary">Pomalšie užívatelské prostredie môže zlepšiť stabilitu</string>
|
||||
<string name="logcat_error">Nepodarilo sa spustiť logcat: </string>
|
||||
<string name="module_enabler_enabled_summary">Pomalší userspace backend môže zlepšiť stabilitu</string>
|
||||
<string name="module_installer_error">Niečo sa pokazilo. Prosím, skúste znova</string>
|
||||
<string name="module_installer_not_found">Pre vaše zariadenie nie sú k dispozícii žiadne moduly</string>
|
||||
<string name="module_installer_title">Stiahni a nainštaluj kernel modul</string>
|
||||
<string name="module_installer_working">Sťahuje a inštalujem…</string>
|
||||
<string name="module_installer_title">Stiahnutie a inštalácia kernelového modulu</string>
|
||||
<string name="module_installer_working">Sťahuje sa a inštaluje sa…</string>
|
||||
<string name="mtu">Maximálna prenosová jednotka</string>
|
||||
<string name="multiple_tunnels_summary_off">Zapnutím jedného tunela vypnete ostatné</string>
|
||||
<string name="multiple_tunnels_summary_on">Môžu byť zapnuté viaceré tunele naraz</string>
|
||||
<string name="multiple_tunnels_title">Povoliť niekoľko tunel naraz</string>
|
||||
<string name="multiple_tunnels_summary_on">Môžu byť zapnuté viaceré tunely naraz</string>
|
||||
<string name="multiple_tunnels_title">Povoliť viacero tunelov naraz</string>
|
||||
<string name="name">Názov</string>
|
||||
<string name="no_config_error">Pokúšam sa vyvolať tunel bez konfigurácie</string>
|
||||
<string name="no_config_error">Pokúšam sa zapnúť tunel bez konfigurácie</string>
|
||||
<string name="no_configs_error">Nenašli sa žiadne konfigurácie</string>
|
||||
<string name="no_tunnels_error">Neexistujú žiadne tunely</string>
|
||||
<string name="parse_error_generic">reťazec</string>
|
||||
@@ -108,7 +108,7 @@
|
||||
<string name="parse_error_inet_endpoint">koncový bod</string>
|
||||
<string name="parse_error_inet_network">IP sieť</string>
|
||||
<string name="parse_error_integer">číslo</string>
|
||||
<string name="parse_error_reason">Nedá sa rozobrať %1$s “%2$s”</string>
|
||||
<string name="parse_error_reason">Nedá sa parsovať %1$s “%2$s”</string>
|
||||
<string name="pre_shared_key">Vopred zdieľaný kľúč</string>
|
||||
<string name="pre_shared_key_enabled">povolené</string>
|
||||
<string name="private_key">Súkromný kľúč</string>
|
||||
@@ -121,10 +121,10 @@
|
||||
<string name="toggle_all">Prepnúť všetko</string>
|
||||
<string name="tools_installer_already">wg a wg-quick už sú nainštalované</string>
|
||||
<string name="tools_installer_initial">Nainštalovať voliteľné nástroje pre skriptovanie</string>
|
||||
<string name="tools_installer_initial_magisk">Nainštalovať voliteľné nástroje pre skriptovanie ako Magisk module</string>
|
||||
<string name="tools_installer_initial_magisk">Nainštalovať voliteľné nástroje pre skriptovanie ako Magisk modul</string>
|
||||
<string name="tools_installer_success_magisk">wg a wg-quick sú nainštalované ako Magisk modul (reštart požadovaný)</string>
|
||||
<string name="tools_installer_title">Inštalácia nástrojov príkazového riadku</string>
|
||||
<string name="tools_installer_working">Inštalácia wg a wg-quick</string>
|
||||
<string name="tools_installer_working">Inštaluje sa wg a wg-quick</string>
|
||||
<string name="tools_unavailable_error">Potrebné nástroje nie sú k dispozícii</string>
|
||||
<string name="transfer">Prenos</string>
|
||||
<string name="transfer_bytes">%d B</string>
|
||||
@@ -141,7 +141,7 @@
|
||||
<string name="tunnel_name">Meno tunelu</string>
|
||||
<string name="tunnel_rename_error">Nepodarilo sa premenovať tunel: %s</string>
|
||||
<string name="tunnel_rename_success">Úspešne premenovaný tunel na “%s”</string>
|
||||
<string name="type_name_kernel_module">Modul jadra</string>
|
||||
<string name="type_name_kernel_module">Kernelový modul</string>
|
||||
<string name="unknown_error">Neznáma chyba</string>
|
||||
<string name="version_summary_unknown">Neznáma %s verzia</string>
|
||||
<string name="version_title">WireGuard pre Android v%s</string>
|
||||
@@ -149,10 +149,10 @@
|
||||
<string name="vpn_start_error">Nepodarilo sa spustiť Android VPN službu</string>
|
||||
<string name="zip_export_error">Nepodarilo sa exportovať tunely: %s</string>
|
||||
<string name="zip_export_success">Uložené ako “%s”</string>
|
||||
<string name="zip_export_summary">Zip súbor bude uložený do priečinka na sťahovanie</string>
|
||||
<string name="zip_export_summary">Zip súbor bude uložený do priečinka stiahnuté</string>
|
||||
<string name="zip_export_title">Export tunelov do zip súboru</string>
|
||||
<string name="biometric_prompt_zip_exporter_title">Overovanie pre export tunelov</string>
|
||||
<string name="biometric_prompt_private_key_title">Overovanie pre zobrazenie privátneho kľúča</string>
|
||||
<string name="biometric_prompt_private_key_title">Authenticate to view private key</string>
|
||||
<string name="biometric_auth_error">Overovanie zlyhalo</string>
|
||||
<string name="biometric_auth_error_reason">Overovanie zlyhalo: %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -107,6 +107,9 @@
|
||||
<string name="tv_select_a_storage_drive">Välj en lagringsenhet</string>
|
||||
<string name="tv_no_file_picker">Installera ett filhanteringsverktyg för att bläddra bland filer</string>
|
||||
<string name="tv_add_tunnel_get_started">Lägg till en tunnel för att komma igång</string>
|
||||
<string name="donate_title">♥ Donera till WireGuard Projektet</string>
|
||||
<string name="donate_summary">Varje bidrag hjälper</string>
|
||||
<string name="donate_google_play_disappointment">Tack för att du stödjer WireGuard Projektet!\n\nPå grund av Googles policyer får vi dessvärre inte till den del av projektets webbsida där du kan göra en donation. Förhoppningsvis kan du hitta dit ändå!\n\nTack igen för ditt bidrag.</string>
|
||||
<string name="disable_config_export_title">Inaktivera export av konfiguration</string>
|
||||
<string name="disable_config_export_description">Inaktivering av konfigurationsexport gör privata nycklar mindre tillgängliga</string>
|
||||
<string name="dns_servers">DNS-servrar</string>
|
||||
@@ -116,6 +119,7 @@
|
||||
<string name="error_down">Fel vid nedtagning av tunnel: %s</string>
|
||||
<string name="error_fetching_apps">Fel vid hämtning av applista: %s</string>
|
||||
<string name="error_root">Vänligen få rootbehörighet och försök igen</string>
|
||||
<string name="error_prepare">Fel vid förberedelse av tunnel: %s</string>
|
||||
<string name="error_up">Fel vid uppstart av tunnel: %s</string>
|
||||
<string name="exclude_private_ips">Uteslut privata IP-adresser</string>
|
||||
<string name="generate_new_private_key">Skapa ny privat nyckel</string>
|
||||
@@ -135,6 +139,8 @@
|
||||
<string name="key_length_explanation_base64">: WireGuard base64 nycklar måste vara 44 tecken (32 bytes)</string>
|
||||
<string name="key_length_explanation_binary">: WireGuard nycklar måste vara 32 bytes</string>
|
||||
<string name="key_length_explanation_hex">: WireGuard hex nycklar måste vara 64 tecken (32 bytes)</string>
|
||||
<string name="latest_handshake">Senaste handskakning</string>
|
||||
<string name="latest_handshake_ago">%s sedan</string>
|
||||
<string name="listen_port">Lyssningsport</string>
|
||||
<string name="log_export_error">Kan inte exportera loggen: %s</string>
|
||||
<string name="log_export_subject">WireGuard Android loggfil</string>
|
||||
@@ -178,6 +184,10 @@
|
||||
<string name="private_key">Privat nyckel</string>
|
||||
<string name="public_key">Offentlig nyckel</string>
|
||||
<string name="qr_code_hint">Tips: generera med `qrencode -t ansiutf8 < tunnel.conf`.</string>
|
||||
<string name="quick_settings_tile_add_title">Lägg till tile i snabbinställningarna</string>
|
||||
<string name="quick_settings_tile_add_summary">Tilen växlar din senaste tunnel mellan på och av</string>
|
||||
<string name="quick_settings_tile_add_failure">Misslyckades med att skapa tile: fel %d</string>
|
||||
<string name="quick_settings_tile_action">Växla tunnel på/av</string>
|
||||
<string name="restore_on_boot_summary_off">Kommer inte ta upp aktiverade tunnlar vid uppstart</string>
|
||||
<string name="restore_on_boot_summary_on">Kommer ta upp aktiverade tunnlar vid uppstart</string>
|
||||
<string name="restore_on_boot_title">Återställ vid uppstart</string>
|
||||
@@ -213,6 +223,7 @@
|
||||
<string name="tunnel_create_success">Lyckades skapa tunnel “%s”</string>
|
||||
<string name="tunnel_error_already_exists">Tunnel ”%s” finns redan</string>
|
||||
<string name="tunnel_error_invalid_name">Ogiltigt namn</string>
|
||||
<string name="tunnel_list_placeholder">Lägg till en tunnel med knappen nedan</string>
|
||||
<string name="tunnel_name">Tunnelns namn</string>
|
||||
<string name="tunnel_on_error">Kunde inte aktivera tunneln (wgTurnOn returnerade %d)</string>
|
||||
<string name="tunnel_dns_failure">Det går inte att lösa DNS-värdnamn: ”%s”</string>
|
||||
@@ -221,6 +232,16 @@
|
||||
<string name="type_name_go_userspace">Användarutrymme för Go</string>
|
||||
<string name="type_name_kernel_module">Kärnmodul</string>
|
||||
<string name="unknown_error">Okänt fel</string>
|
||||
<string name="updater_avalable">Det finns en uppdatering till appen. Vänligen uppdatera nu.</string>
|
||||
<string name="updater_action">Ladda ner & uppdatera</string>
|
||||
<string name="updater_rechecking">Hämtar uppdateringens metadata…</string>
|
||||
<string name="updater_download_progress">Laddar ner uppdatering: %1$s / %2$s (%3$.2f%%)</string>
|
||||
<string name="updater_download_progress_nototal">Laddar ner uppdatering: %s</string>
|
||||
<string name="updater_installing">Installerar uppdatering…</string>
|
||||
<string name="updater_failure">Uppdatering misslyckades: %s. Försöker igen inom kort…</string>
|
||||
<string name="updater_corrupt_title">Applikationen är korrupt</string>
|
||||
<string name="updater_corrupt_message">Applikationen är korrupt. Vänligen ladda ner en APK från hemsidan länkad nedan. Avinstallera därefter denna applikation och installera den nerladdade APKn.</string>
|
||||
<string name="updater_corrupt_navigate">Öppna hemsida</string>
|
||||
<string name="version_summary">%1$s bakstycke %2$s</string>
|
||||
<string name="version_summary_checking">Kontrollerar %s backstycke utgåva</string>
|
||||
<string name="version_summary_unknown">Okänd %s utgåva</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>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<style name="AppTheme" parent="AppThemeBase">
|
||||
<item name="android:statusBarColor">?android:colorBackground</item>
|
||||
<item name="android:windowLightStatusBar">@bool/light_status_bar</item>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<style name="AppTheme" parent="AppThemeBase">
|
||||
<item name="android:statusBarColor">?android:colorBackground</item>
|
||||
<item name="android:windowLightStatusBar">@bool/light_status_bar</item>
|
||||
|
||||
@@ -94,6 +94,9 @@
|
||||
<string name="tv_select_a_storage_drive">Chọn bộ lưu trữ</string>
|
||||
<string name="tv_no_file_picker">Vui lòng cài đặt tệp tiện ích lưu trữ để tìm kiếm các tệp</string>
|
||||
<string name="tv_add_tunnel_get_started">Thêm một tunnel để bắt đầu</string>
|
||||
<string name="donate_title">❤️ Đóng góp cho Dự án Wireguard</string>
|
||||
<string name="donate_summary">Mọi đóng góp đều giúp ích</string>
|
||||
<string name="donate_google_play_disappointment">Cảm ơn bạn đã ủng hộ WireGuard!\n\nThật tiếc, dựa trên điều khoản của Google, chúng tôi không thể đưa vào liên kết dẫn đến trang đóng góp ở trang chủ của dự án. Mong rằng bạn có thể tìm cách cho việc này!\n\nXin cảm ơn bạn một lần nữa vì đã đóng góp.</string>
|
||||
<string name="disable_config_export_title">Vô hiệu hóa xuất cấu hình</string>
|
||||
<string name="disable_config_export_description">Vô hiệu hóa xuất cấu hình sẽ giúp giảm khả năng truy cập vào private keys</string>
|
||||
<string name="dns_servers">DNS servers</string>
|
||||
@@ -103,9 +106,34 @@
|
||||
<string name="error_down">Có lỗi khi tắt tunnel: %s</string>
|
||||
<string name="error_fetching_apps">Lỗi khi lấy danh sách ứng dụng: %s</string>
|
||||
<string name="error_root">Vui lòng truy cập bằng quyền root và thử lại</string>
|
||||
<string name="error_prepare">Lỗi khi chuẩn bị tunnel: %s</string>
|
||||
<string name="error_up">Có lỗi khi bật tunnel: %s</string>
|
||||
<string name="exclude_private_ips">Loại trừ IPs private</string>
|
||||
<string name="generate_new_private_key">Tạo private key mới</string>
|
||||
<string name="generic_error">Lỗi \"%s\" không xác định</string>
|
||||
<string name="hint_automatic">(tự động)</string>
|
||||
<string name="hint_generated">(được tạo tự động)</string>
|
||||
<string name="hint_optional">(tùy chọn)</string>
|
||||
<string name="hint_optional_discouraged">(tùy chọn, không khuyến khích)</string>
|
||||
<string name="hint_random">(ngẫu nhiên)</string>
|
||||
<string name="illegal_filename_error">Tên file không hợp lệ \"%s\"</string>
|
||||
<string name="import_error">Không thể nhập tunnel: %s</string>
|
||||
<string name="import_from_qr_code">Nhập tunnel từ mã QR</string>
|
||||
<string name="import_success">Đã nhập \"%s\"</string>
|
||||
<string name="interface_title">Giao diện</string>
|
||||
<string name="key_contents_error">Kí tự không hợp lệ trong khoá</string>
|
||||
<string name="key_length_error">Độ dài khoá không hợp lệ</string>
|
||||
<string name="key_length_explanation_base64">: Khoá WireGuard base64 phải đủ 44 ký tự (32 bytes)</string>
|
||||
<string name="key_length_explanation_binary">: Khoá WireGuard phải đủ 32 bytes</string>
|
||||
<string name="key_length_explanation_hex">: Khoá WireGuard hex phải đủ 64 ký tự (32 bytes)</string>
|
||||
<string name="latest_handshake">Lần bắt tay cuối</string>
|
||||
<string name="latest_handshake_ago">%s giây trước</string>
|
||||
<string name="listen_port">Cổng</string>
|
||||
<string name="log_export_error">Không thể xuất nhật ký: %s</string>
|
||||
<string name="log_export_subject">File nhật ký WireGuard Android</string>
|
||||
<string name="log_export_success">Đã lưu vào \"%s\"</string>
|
||||
<string name="log_export_title">Xuất file nhật ký</string>
|
||||
<string name="log_saver_activity_label">Lưu nhật ký</string>
|
||||
<string name="parse_error_inet_address">Địa chỉ IP</string>
|
||||
<string name="peer">Đồng trang lứa</string>
|
||||
</resources>
|
||||
|
||||
@@ -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>
|
||||
@@ -168,6 +171,10 @@
|
||||
<string name="private_key">私钥</string>
|
||||
<string name="public_key">公钥</string>
|
||||
<string name="qr_code_hint">提示:使用命令 `qrencode -t ansiutf8 < tunnel.conf` 生成二维码</string>
|
||||
<string name="quick_settings_tile_add_title">添加磁贴到快速设置面板</string>
|
||||
<string name="quick_settings_tile_add_summary">通过快捷磁贴开启/关闭上次使用的隧道</string>
|
||||
<string name="quick_settings_tile_add_failure">无法添加快捷磁贴:错误 %d</string>
|
||||
<string name="quick_settings_tile_action">开启/关闭隧道</string>
|
||||
<string name="restore_on_boot_summary_off">未启用</string>
|
||||
<string name="restore_on_boot_summary_on">设备启动时自动开启上次使用的隧道</string>
|
||||
<string name="restore_on_boot_title">启动时恢复</string>
|
||||
@@ -212,6 +219,16 @@
|
||||
<string name="type_name_go_userspace">Go userspace</string>
|
||||
<string name="type_name_kernel_module">Kernel module</string>
|
||||
<string name="unknown_error">未知错误</string>
|
||||
<string name="updater_avalable">WireGuard 可以更新了,请立即更新。</string>
|
||||
<string name="updater_action">下载 & 更新</string>
|
||||
<string name="updater_rechecking">正在获取更新元数据…</string>
|
||||
<string name="updater_download_progress">正在下载更新:%1$s / %2$s (%3$.2f%%)</string>
|
||||
<string name="updater_download_progress_nototal">正在下载更新:%s</string>
|
||||
<string name="updater_installing">正在安装更新…</string>
|
||||
<string name="updater_failure">更新失败:%s。将在稍后重试…</string>
|
||||
<string name="updater_corrupt_title">应用损坏</string>
|
||||
<string name="updater_corrupt_message">此应用已损坏。请从下方链接的网站中重新下载 APK,然后卸载此应用并重新安装。</string>
|
||||
<string name="updater_corrupt_navigate">打开网站</string>
|
||||
<string name="version_summary">%1$s backend %2$s</string>
|
||||
<string name="version_summary_checking">正在检查 %s backend 版本</string>
|
||||
<string name="version_summary_unknown">未知的 %s 版本</string>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -183,6 +185,10 @@
|
||||
<string name="private_key">Private key</string>
|
||||
<string name="public_key">Public key</string>
|
||||
<string name="qr_code_hint">Tip: generate with `qrencode -t ansiutf8 < tunnel.conf`.</string>
|
||||
<string name="quick_settings_tile_add_title">Add tile to quick settings panel</string>
|
||||
<string name="quick_settings_tile_add_summary">The shortcut tile toggles the most recent tunnel</string>
|
||||
<string name="quick_settings_tile_add_failure">Unable to add shortcut tile: error %d</string>
|
||||
<string name="quick_settings_tile_action">Toggle tunnel</string>
|
||||
<string name="restore_on_boot_summary_off">Will not bring up enabled tunnels at boot</string>
|
||||
<string name="restore_on_boot_summary_on">Will bring up enabled tunnels at boot</string>
|
||||
<string name="restore_on_boot_title">Restore on boot</string>
|
||||
@@ -227,6 +233,16 @@
|
||||
<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 & 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="updater_corrupt_title">Application Corrupt</string>
|
||||
<string name="updater_corrupt_message">This application is corrupt. Please re-download the APK from the website linked below. After, uninstall this application, and reinstall it from the downloaded APK.</string>
|
||||
<string name="updater_corrupt_navigate">Open Website</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>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
<resources>
|
||||
|
||||
<style name="WireGuardTheme" parent="Theme.Material3.Light">
|
||||
<item name="colorPrimary">@color/md_theme_light_primary</item>
|
||||
<item name="colorOnPrimary">@color/md_theme_light_onPrimary</item>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user