mirror of
https://github.com/amnezia-vpn/amneziawg-android.git
synced 2026-07-03 14:05:13 +02:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 79c5eb41c3 | |||
| 9e30c7d2e9 | |||
| d80129ba46 | |||
| 6907663f07 | |||
| dca96a52b4 | |||
| cf4fca8086 | |||
| 318f36a205 | |||
| 87aa73cc64 | |||
| 4e28d57aee | |||
| 6d67a3d5f5 | |||
| 1b5f4d6dfd | |||
| bed2f2e5d6 | |||
| 7d91666860 | |||
| 8800f861ed | |||
| 99336f6552 | |||
| 0b7b65296b | |||
| e2f931b4cb | |||
| d6ad7d11d0 |
@@ -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.7.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'
|
||||
coroutinesVersion = '1.6.4'
|
||||
datastoreVersion = '1.0.0'
|
||||
desugarVersion = '1.1.5'
|
||||
fragmentVersion = '1.5.7'
|
||||
jsr305Version = '3.0.2'
|
||||
junitVersion = '4.13.2'
|
||||
lifecycleRuntimeKtxVersion = '2.6.1'
|
||||
materialComponentsVersion = '1.8.0'
|
||||
preferenceVersion = '1.2.0'
|
||||
zxingEmbeddedVersion = '4.3.0'
|
||||
|
||||
groupName = 'com.wireguard.android'
|
||||
}
|
||||
}
|
||||
|
||||
tasks {
|
||||
wrapper {
|
||||
gradleVersion = "8.1.1"
|
||||
distributionSha256Sum = "e111cb9948407e26351227dabce49822fb88c37ee72f1d1582a69c68af2e702f"
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
wireguardVersionCode=502
|
||||
wireguardVersionName=1.0.20230504
|
||||
wireguardPackageName=com.wireguard.android
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
[versions]
|
||||
agp = "8.0.1"
|
||||
kotlin = "1.8.21"
|
||||
|
||||
[libraries]
|
||||
androidx-activity-ktx = "androidx.activity:activity-ktx:1.7.1"
|
||||
androidx-annotation = "androidx.annotation:annotation:1.6.0"
|
||||
androidx-appcompat = "androidx.appcompat:appcompat:1.6.1"
|
||||
androidx-biometric = "androidx.biometric:biometric:1.1.0"
|
||||
androidx-collection = "androidx.collection:collection:1.2.0"
|
||||
androidx-constraintlayout = "androidx.constraintlayout:constraintlayout:2.1.4"
|
||||
androidx-coordinatorlayout = "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
||||
androidx-core-ktx = "androidx.core:core-ktx:1.10.0"
|
||||
androidx-datastore-preferences = "androidx.datastore:datastore-preferences:1.0.0"
|
||||
androidx-fragment-ktx = "androidx.fragment:fragment-ktx:1.5.7"
|
||||
androidx-lifecycle-runtime-ktx = "androidx.lifecycle:lifecycle-runtime-ktx:2.6.1"
|
||||
androidx-preference-ktx = "androidx.preference:preference-ktx:1.2.0"
|
||||
desugarJdkLibs = "com.android.tools:desugar_jdk_libs:2.0.3"
|
||||
google-material = "com.google.android.material:material:1.8.0"
|
||||
jsr305 = "com.google.code.findbugs:jsr305:3.0.2"
|
||||
junit = "junit:junit:4.13.2"
|
||||
kotlinx-coroutines-android = "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
|
||||
zxing-android-embedded = "com.journeyapps:zxing-android-embedded:4.3.0"
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
|
||||
@@ -1,28 +0,0 @@
|
||||
pluginManagement {
|
||||
def agpVersion = '8.0.0'
|
||||
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,130 @@
|
||||
@file:Suppress("UnstableApiUsage")
|
||||
import org.gradle.api.tasks.testing.logging.TestLogEvent
|
||||
|
||||
val pkg: String = providers.gradleProperty("wireguardPackageName").get()
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
`maven-publish`
|
||||
signing
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = 33
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
namespace = "${pkg}.tunnel"
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path("tools/CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
testOptions.unitTests.all {
|
||||
it.testLogging { events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) }
|
||||
}
|
||||
buildTypes {
|
||||
all {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
targets("libwg-go.so", "libwg.so", "libwg-quick.so")
|
||||
arguments("-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}")
|
||||
}
|
||||
}
|
||||
}
|
||||
release {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments("-DANDROID_PACKAGE_NAME=${pkg}")
|
||||
}
|
||||
}
|
||||
}
|
||||
debug {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments("-DANDROID_PACKAGE_NAME=${pkg}.debug")
|
||||
}
|
||||
}
|
||||
}
|
||||
create("googleplay") {
|
||||
initWith(getByName("release"))
|
||||
}
|
||||
}
|
||||
lint {
|
||||
disable += "LongLogTag"
|
||||
disable += "NewApi"
|
||||
}
|
||||
publishing {
|
||||
singleVariant("release") {
|
||||
withJavadocJar()
|
||||
withSourcesJar()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.androidx.annotation)
|
||||
implementation(libs.androidx.collection)
|
||||
compileOnly(libs.jsr305)
|
||||
testImplementation(libs.junit)
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
register<MavenPublication>("release") {
|
||||
groupId = pkg
|
||||
artifactId = "tunnel"
|
||||
version = providers.gradleProperty("wireguardVersionName").get()
|
||||
afterEvaluate {
|
||||
from(components["release"])
|
||||
}
|
||||
pom {
|
||||
name.set("WireGuard Tunnel Library")
|
||||
description.set("Embeddable tunnel library for WireGuard for Android")
|
||||
url.set("https://www.wireguard.com/")
|
||||
|
||||
licenses {
|
||||
license {
|
||||
name.set("The Apache Software License, Version 2.0")
|
||||
url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
|
||||
distribution.set("repo")
|
||||
}
|
||||
}
|
||||
scm {
|
||||
connection.set("scm:git:https://git.zx2c4.com/wireguard-android")
|
||||
developerConnection.set("scm:git:https://git.zx2c4.com/wireguard-android")
|
||||
url.set("https://git.zx2c4.com/wireguard-android")
|
||||
}
|
||||
developers {
|
||||
organization {
|
||||
name.set("WireGuard")
|
||||
url.set("https://www.wireguard.com/")
|
||||
}
|
||||
developer {
|
||||
name.set("WireGuard")
|
||||
email.set("team@wireguard.com")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
maven {
|
||||
name = "sonatype"
|
||||
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
|
||||
credentials {
|
||||
username = providers.environmentVariable("SONATYPE_USER").orNull
|
||||
password = providers.environmentVariable("SONATYPE_PASSWORD").orNull
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signing {
|
||||
useGpgCmd()
|
||||
sign(publishing.publications)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,81 +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
|
||||
|
||||
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
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles "proguard-android-optimize.txt"
|
||||
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,89 @@
|
||||
@file:Suppress("UnstableApiUsage")
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
val pkg: String = providers.gradleProperty("wireguardPackageName").get()
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.kapt)
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = 33
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
dataBinding = true
|
||||
viewBinding = true
|
||||
}
|
||||
namespace = pkg
|
||||
defaultConfig {
|
||||
applicationId = pkg
|
||||
minSdk = 21
|
||||
targetSdk = 33
|
||||
versionCode = providers.gradleProperty("wireguardVersionCode").get().toInt()
|
||||
versionName = providers.gradleProperty("wireguardVersionName").get()
|
||||
buildConfigField("int", "MIN_SDK_VERSION", minSdk.toString())
|
||||
buildConfigField("boolean", "IS_GOOGLE_PLAY", false.toString())
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles("proguard-android-optimize.txt")
|
||||
packaging {
|
||||
resources {
|
||||
excludes += "DebugProbesKt.bin"
|
||||
excludes += "kotlin-tooling-metadata.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix = ".debug"
|
||||
versionNameSuffix = "-debug"
|
||||
}
|
||||
create("googleplay") {
|
||||
initWith(getByName("release"))
|
||||
buildConfigField("boolean", "IS_GOOGLE_PLAY", true.toString())
|
||||
}
|
||||
}
|
||||
lint {
|
||||
disable += "LongLogTag"
|
||||
warning += "MissingTranslation"
|
||||
warning += "ImpliedQuantity"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":tunnel"))
|
||||
implementation(libs.androidx.activity.ktx)
|
||||
implementation(libs.androidx.annotation)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.androidx.constraintlayout)
|
||||
implementation(libs.androidx.coordinatorlayout)
|
||||
implementation(libs.androidx.biometric)
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.fragment.ktx)
|
||||
implementation(libs.androidx.preference.ktx)
|
||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
implementation(libs.google.material)
|
||||
implementation(libs.zxing.android.embedded)
|
||||
implementation(libs.kotlinx.coroutines.android)
|
||||
coreLibraryDesugaring(libs.desugarJdkLibs)
|
||||
}
|
||||
|
||||
tasks.withType<JavaCompile>().configureEach {
|
||||
options.compilerArgs.add("-Xlint:unchecked")
|
||||
options.isDeprecation = true
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile>().configureEach {
|
||||
compilerOptions.jvmTarget.set(JvmTarget.JVM_17)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" tools:node="remove" />
|
||||
<application>
|
||||
<receiver android:name=".updater.Updater$AppUpdatedReceiver" tools:node="remove" />
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -6,6 +6,7 @@
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28"
|
||||
@@ -106,6 +107,12 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".updater.Updater$AppUpdatedReceiver" android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".model.TunnelManager$IntentReceiver"
|
||||
android:permission="${applicationId}.permission.CONTROL_TUNNELS"
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.wireguard.android.backend.GoBackend
|
||||
import com.wireguard.android.backend.WgQuickBackend
|
||||
import com.wireguard.android.configStore.FileConfigStore
|
||||
import com.wireguard.android.model.TunnelManager
|
||||
import com.wireguard.android.updater.Updater
|
||||
import com.wireguard.android.util.RootShell
|
||||
import com.wireguard.android.util.ToolsInstaller
|
||||
import com.wireguard.android.util.UserKnobs
|
||||
@@ -115,6 +116,7 @@ class Application : android.app.Application() {
|
||||
Log.e(TAG, Log.getStackTraceString(e))
|
||||
}
|
||||
}
|
||||
Updater.monitorForUpdates()
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
StrictMode.setVmPolicy(VmPolicy.Builder().detectAll().penaltyLog().build())
|
||||
|
||||
@@ -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)")
|
||||
|
||||
@@ -33,6 +33,7 @@ import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter.RowC
|
||||
import com.wireguard.android.databinding.TunnelListFragmentBinding
|
||||
import com.wireguard.android.databinding.TunnelListItemBinding
|
||||
import com.wireguard.android.model.ObservableTunnel
|
||||
import com.wireguard.android.updater.SnackbarUpdateShower
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import com.wireguard.android.util.QrCodeFromFileScanner
|
||||
import com.wireguard.android.util.TunnelImporter
|
||||
@@ -122,6 +123,8 @@ class TunnelListFragment : BaseFragment() {
|
||||
backPressedCallback = requireActivity().onBackPressedDispatcher.addCallback(this) { actionMode?.finish() }
|
||||
backPressedCallback?.isEnabled = false
|
||||
|
||||
SnackbarUpdateShower.attachToActivity(requireActivity(), binding?.mainContainer!!, binding?.createFab)
|
||||
|
||||
return binding?.root
|
||||
}
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@ package com.wireguard.android.preference
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.widget.Toast
|
||||
import androidx.preference.Preference
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.wireguard.android.BuildConfig
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
|
||||
@@ -22,21 +22,8 @@ class DonatePreference(context: Context, attrs: AttributeSet?) : Preference(cont
|
||||
override fun getTitle() = context.getString(R.string.donate_title)
|
||||
|
||||
override fun onClick() {
|
||||
val installer = try {
|
||||
val packageName = context.packageName
|
||||
val pm = context.packageManager
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
pm.getInstallSourceInfo(packageName).installingPackageName ?: ""
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
pm.getInstallerPackageName(packageName) ?: ""
|
||||
}
|
||||
} catch (_: Throwable) {
|
||||
""
|
||||
}
|
||||
|
||||
/* Google Play Store forbids links to our donation page. */
|
||||
if (installer == "com.android.vending") {
|
||||
if (BuildConfig.IS_GOOGLE_PLAY) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.donate_title)
|
||||
.setMessage(R.string.donate_google_play_disappointment)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.updater
|
||||
|
||||
import android.view.View
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.snackbar.BaseTransientBottomBar
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.wireguard.android.BuildConfig
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import com.wireguard.android.util.QuantityFormatter
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
object SnackbarUpdateShower {
|
||||
private class SwapableSnackbar(activity: FragmentActivity, view: View, anchor: View?) {
|
||||
val actionSnackbar = makeSnackbar(activity, view, anchor)
|
||||
val statusSnackbar = makeSnackbar(activity, view, anchor)
|
||||
var showingAction: Boolean = false
|
||||
var showingStatus: Boolean = false
|
||||
|
||||
private fun makeSnackbar(activity: FragmentActivity, view: View, anchor: View?): Snackbar {
|
||||
val snackbar = Snackbar.make(activity, view, "", Snackbar.LENGTH_INDEFINITE)
|
||||
if (anchor != null)
|
||||
snackbar.anchorView = anchor
|
||||
snackbar.setTextMaxLines(6)
|
||||
snackbar.behavior = object : BaseTransientBottomBar.Behavior() {
|
||||
override fun canSwipeDismissView(child: View): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
snackbar.addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
|
||||
override fun onDismissed(snackbar: Snackbar?, @DismissEvent event: Int) {
|
||||
super.onDismissed(snackbar, event)
|
||||
if (event == DISMISS_EVENT_MANUAL || event == DISMISS_EVENT_ACTION ||
|
||||
(snackbar == actionSnackbar && !showingAction) ||
|
||||
(snackbar == statusSnackbar && !showingStatus)
|
||||
)
|
||||
return
|
||||
activity.lifecycleScope.launch {
|
||||
delay(5.seconds)
|
||||
snackbar?.show()
|
||||
}
|
||||
}
|
||||
})
|
||||
return snackbar
|
||||
}
|
||||
|
||||
fun showAction(text: String, action: String, listener: View.OnClickListener) {
|
||||
if (showingStatus) {
|
||||
showingStatus = false
|
||||
statusSnackbar.dismiss()
|
||||
}
|
||||
actionSnackbar.setText(text)
|
||||
actionSnackbar.setAction(action, listener)
|
||||
if (!showingAction) {
|
||||
actionSnackbar.show()
|
||||
showingAction = true
|
||||
}
|
||||
}
|
||||
|
||||
fun showText(text: String) {
|
||||
if (showingAction) {
|
||||
showingAction = false
|
||||
actionSnackbar.dismiss()
|
||||
}
|
||||
statusSnackbar.setText(text)
|
||||
if (!showingStatus) {
|
||||
statusSnackbar.show()
|
||||
showingStatus = true
|
||||
}
|
||||
}
|
||||
|
||||
fun dismiss() {
|
||||
actionSnackbar.dismiss()
|
||||
statusSnackbar.dismiss()
|
||||
showingAction = false
|
||||
showingStatus = false
|
||||
}
|
||||
}
|
||||
|
||||
fun attachToActivity(activity: FragmentActivity, view: View, anchor: View?) {
|
||||
if (BuildConfig.IS_GOOGLE_PLAY)
|
||||
return
|
||||
|
||||
val snackbar = SwapableSnackbar(activity, view, anchor)
|
||||
val context = activity.applicationContext
|
||||
|
||||
var lastUserIntervention: Updater.Progress.NeedsUserIntervention? = null
|
||||
val intentLauncher = activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
lastUserIntervention?.markAsDone()
|
||||
}
|
||||
|
||||
Updater.state.onEach { progress ->
|
||||
when (progress) {
|
||||
is Updater.Progress.Complete ->
|
||||
snackbar.dismiss()
|
||||
|
||||
is Updater.Progress.Available ->
|
||||
snackbar.showAction(
|
||||
context.getString(R.string.updater_avalable),
|
||||
context.getString(R.string.updater_action)
|
||||
) {
|
||||
progress.update()
|
||||
}
|
||||
|
||||
is Updater.Progress.NeedsUserIntervention -> {
|
||||
lastUserIntervention = progress
|
||||
intentLauncher.launch(progress.intent)
|
||||
}
|
||||
|
||||
is Updater.Progress.Installing ->
|
||||
snackbar.showText(context.getString(R.string.updater_installing))
|
||||
|
||||
is Updater.Progress.Rechecking ->
|
||||
snackbar.showText(context.getString(R.string.updater_rechecking))
|
||||
|
||||
is Updater.Progress.Downloading -> {
|
||||
if (progress.bytesTotal != 0UL) {
|
||||
snackbar.showText(
|
||||
context.getString(
|
||||
R.string.updater_download_progress,
|
||||
QuantityFormatter.formatBytes(progress.bytesDownloaded.toLong()),
|
||||
QuantityFormatter.formatBytes(progress.bytesTotal.toLong()),
|
||||
progress.bytesDownloaded.toFloat() * 100.0 / progress.bytesTotal.toFloat()
|
||||
)
|
||||
)
|
||||
} else {
|
||||
snackbar.showText(
|
||||
context.getString(
|
||||
R.string.updater_download_progress_nototal,
|
||||
QuantityFormatter.formatBytes(progress.bytesDownloaded.toLong())
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is Updater.Progress.Failure -> {
|
||||
snackbar.showText(
|
||||
context.getString(
|
||||
R.string.updater_failure,
|
||||
ErrorMessages[progress.error]
|
||||
)
|
||||
)
|
||||
delay(5.seconds)
|
||||
progress.retry()
|
||||
}
|
||||
}
|
||||
}.launchIn(activity.lifecycleScope)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,434 @@
|
||||
/*
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.updater
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.os.Build
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.IntentCompat
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.BuildConfig
|
||||
import com.wireguard.android.activity.MainActivity
|
||||
import com.wireguard.android.util.UserKnobs
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.IOException
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.InvalidKeyException
|
||||
import java.security.InvalidParameterException
|
||||
import java.security.MessageDigest
|
||||
import java.util.UUID
|
||||
import kotlin.math.max
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
object Updater {
|
||||
private const val TAG = "WireGuard/Updater"
|
||||
private const val LATEST_VERSION_URL =
|
||||
"https://download.wireguard.com/android-client/latest.sig"
|
||||
private const val APK_PATH_URL = "https://download.wireguard.com/android-client/%s"
|
||||
private val APK_NAME_PREFIX = BuildConfig.APPLICATION_ID.removeSuffix(".debug") + "-"
|
||||
private const val APK_NAME_SUFFIX = ".apk"
|
||||
private const val RELEASE_PUBLIC_KEY_BASE64 =
|
||||
"RWTAzwGRYr3EC9px0Ia3fbttz8WcVN6wrOwWp2delz4el6SI8XmkKSMp"
|
||||
private val CURRENT_VERSION = Version(BuildConfig.VERSION_NAME.removeSuffix("-debug"))
|
||||
|
||||
private val updaterScope = CoroutineScope(Job() + Dispatchers.IO)
|
||||
|
||||
sealed class Progress {
|
||||
object Complete : Progress()
|
||||
class Available(val version: String) : Progress() {
|
||||
fun update() {
|
||||
Application.getCoroutineScope().launch {
|
||||
UserKnobs.setUpdaterNewerVersionConsented(version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object Rechecking : Progress()
|
||||
class Downloading(val bytesDownloaded: ULong, val bytesTotal: ULong) : Progress()
|
||||
object Installing : Progress()
|
||||
class NeedsUserIntervention(val intent: Intent, private val id: Int) : Progress() {
|
||||
|
||||
private suspend fun installerActive(): Boolean {
|
||||
if (mutableState.firstOrNull() != this@NeedsUserIntervention)
|
||||
return true
|
||||
try {
|
||||
if (Application.get().packageManager.packageInstaller.getSessionInfo(id)?.isActive == true)
|
||||
return true
|
||||
} catch (_: SecurityException) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun markAsDone() {
|
||||
Application.getCoroutineScope().launch {
|
||||
if (installerActive())
|
||||
return@launch
|
||||
delay(7.seconds)
|
||||
if (installerActive())
|
||||
return@launch
|
||||
emitProgress(Failure(Exception("Ignored by user")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Failure(val error: Throwable) : Progress() {
|
||||
fun retry() {
|
||||
updaterScope.launch {
|
||||
downloadAndUpdateWrapErrors()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val mutableState = MutableStateFlow<Progress>(Progress.Complete)
|
||||
val state = mutableState.asStateFlow()
|
||||
|
||||
private suspend fun emitProgress(progress: Progress, force: Boolean = false) {
|
||||
if (force || mutableState.firstOrNull()?.javaClass != progress.javaClass)
|
||||
mutableState.emit(progress)
|
||||
}
|
||||
|
||||
private class Sha256Digest(hex: String) {
|
||||
val bytes: ByteArray
|
||||
|
||||
init {
|
||||
if (hex.length != 64)
|
||||
throw InvalidParameterException("SHA256 hashes must be 32 bytes long")
|
||||
bytes = hex.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
private class Version(version: String) : Comparable<Version> {
|
||||
val parts: ULongArray
|
||||
|
||||
init {
|
||||
val strParts = version.split(".")
|
||||
if (strParts.isEmpty())
|
||||
throw InvalidParameterException("Version has no parts")
|
||||
parts = ULongArray(strParts.size)
|
||||
for (i in parts.indices) {
|
||||
parts[i] = strParts[i].toULong()
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return parts.joinToString(".")
|
||||
}
|
||||
|
||||
override fun compareTo(other: Version): Int {
|
||||
for (i in 0 until max(parts.size, other.parts.size)) {
|
||||
val lhsPart = if (i < parts.size) parts[i] else 0UL
|
||||
val rhsPart = if (i < other.parts.size) other.parts[i] else 0UL
|
||||
if (lhsPart > rhsPart)
|
||||
return 1
|
||||
else if (lhsPart < rhsPart)
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
private class Update(val fileName: String, val version: Version, val hash: Sha256Digest)
|
||||
|
||||
private fun versionOfFile(name: String): Version? {
|
||||
if (!name.startsWith(APK_NAME_PREFIX) || !name.endsWith(APK_NAME_SUFFIX))
|
||||
return null
|
||||
return try {
|
||||
Version(name.substring(APK_NAME_PREFIX.length, name.length - APK_NAME_SUFFIX.length))
|
||||
} catch (_: Throwable) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun verifySignedFileList(signifyDigest: String): List<Update> {
|
||||
val updates = ArrayList<Update>(1)
|
||||
val publicKeyBytes = Base64.decode(RELEASE_PUBLIC_KEY_BASE64, Base64.DEFAULT)
|
||||
if (publicKeyBytes == null || publicKeyBytes.size != 32 + 10 || publicKeyBytes[0] != 'E'.code.toByte() || publicKeyBytes[1] != 'd'.code.toByte())
|
||||
throw InvalidKeyException("Invalid public key")
|
||||
val lines = signifyDigest.split("\n", limit = 3)
|
||||
if (lines.size != 3)
|
||||
throw InvalidParameterException("Invalid signature format: too few lines")
|
||||
if (!lines[0].startsWith("untrusted comment: "))
|
||||
throw InvalidParameterException("Invalid signature format: missing comment")
|
||||
val signatureBytes = Base64.decode(lines[1], Base64.DEFAULT)
|
||||
if (signatureBytes == null || signatureBytes.size != 64 + 10)
|
||||
throw InvalidParameterException("Invalid signature format: wrong sized or missing signature")
|
||||
for (i in 0..9) {
|
||||
if (signatureBytes[i] != publicKeyBytes[i])
|
||||
throw InvalidParameterException("Invalid signature format: wrong signer")
|
||||
}
|
||||
if (!Ed25519.verify(
|
||||
lines[2].toByteArray(StandardCharsets.UTF_8),
|
||||
signatureBytes.sliceArray(10 until 10 + 64),
|
||||
publicKeyBytes.sliceArray(10 until 10 + 32)
|
||||
)
|
||||
)
|
||||
throw SecurityException("Invalid signature")
|
||||
for (line in lines[2].split("\n").dropLastWhile { it.isEmpty() }) {
|
||||
val components = line.split(" ", limit = 2)
|
||||
if (components.size != 2)
|
||||
throw InvalidParameterException("Invalid file list format: too few components")
|
||||
/* If version is null, it's not a file we understand, but still a legitimate entry, so don't throw. */
|
||||
val version = versionOfFile(components[1]) ?: continue
|
||||
updates.add(Update(components[1], version, Sha256Digest(components[0])))
|
||||
}
|
||||
return updates
|
||||
}
|
||||
|
||||
private fun checkForUpdates(): Update? {
|
||||
val connection = URL(LATEST_VERSION_URL).openConnection() as HttpURLConnection
|
||||
connection.setRequestProperty("User-Agent", Application.USER_AGENT)
|
||||
connection.connect()
|
||||
if (connection.responseCode != HttpURLConnection.HTTP_OK)
|
||||
throw IOException(connection.responseMessage)
|
||||
var fileListBytes = ByteArray(1024 * 512 /* 512 KiB */)
|
||||
connection.inputStream.use {
|
||||
val len = it.read(fileListBytes)
|
||||
if (len <= 0)
|
||||
throw IOException("File list is empty")
|
||||
fileListBytes = fileListBytes.sliceArray(0 until len)
|
||||
}
|
||||
return verifySignedFileList(fileListBytes.decodeToString()).maxByOrNull { it.version }
|
||||
}
|
||||
|
||||
private suspend fun downloadAndUpdate() = withContext(Dispatchers.IO) {
|
||||
val receiver = InstallReceiver()
|
||||
val context = Application.get().applicationContext
|
||||
val pendingIntent = withContext(Dispatchers.Main) {
|
||||
ContextCompat.registerReceiver(
|
||||
context,
|
||||
receiver,
|
||||
IntentFilter(receiver.sessionId),
|
||||
ContextCompat.RECEIVER_NOT_EXPORTED
|
||||
)
|
||||
PendingIntent.getBroadcast(
|
||||
context,
|
||||
0,
|
||||
Intent(receiver.sessionId).setPackage(context.packageName),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
|
||||
)
|
||||
}
|
||||
|
||||
emitProgress(Progress.Rechecking)
|
||||
val update = checkForUpdates()
|
||||
if (update == null || update.version <= CURRENT_VERSION) {
|
||||
emitProgress(Progress.Complete)
|
||||
return@withContext
|
||||
}
|
||||
|
||||
emitProgress(Progress.Downloading(0UL, 0UL), true)
|
||||
val connection =
|
||||
URL(APK_PATH_URL.format(update.fileName)).openConnection() as HttpURLConnection
|
||||
connection.setRequestProperty("User-Agent", Application.USER_AGENT)
|
||||
connection.connect()
|
||||
if (connection.responseCode != HttpURLConnection.HTTP_OK)
|
||||
throw IOException("Update could not be fetched: ${connection.responseCode}")
|
||||
|
||||
var downloadedByteLen: ULong = 0UL
|
||||
val totalByteLen =
|
||||
(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) connection.contentLengthLong else connection.contentLength).toLong()
|
||||
.toULong()
|
||||
val fileBytes = ByteArray(1024 * 32 /* 32 KiB */)
|
||||
val digest = MessageDigest.getInstance("SHA-256")
|
||||
emitProgress(Progress.Downloading(downloadedByteLen, totalByteLen), true)
|
||||
|
||||
val installer = context.packageManager.packageInstaller
|
||||
val params =
|
||||
PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
params.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED)
|
||||
params.setAppPackageName(context.packageName) /* Enforces updates; disallows new apps. */
|
||||
val session = installer.openSession(installer.createSession(params))
|
||||
var sessionFailure = true
|
||||
try {
|
||||
val installDest = session.openWrite(receiver.sessionId, 0, -1)
|
||||
|
||||
installDest.use { dest ->
|
||||
connection.inputStream.use { src ->
|
||||
while (true) {
|
||||
val readLen = src.read(fileBytes)
|
||||
if (readLen <= 0)
|
||||
break
|
||||
|
||||
digest.update(fileBytes, 0, readLen)
|
||||
dest.write(fileBytes, 0, readLen)
|
||||
|
||||
downloadedByteLen += readLen.toUInt()
|
||||
emitProgress(Progress.Downloading(downloadedByteLen, totalByteLen), true)
|
||||
|
||||
if (downloadedByteLen >= 1024UL * 1024UL * 100UL /* 100 MiB */)
|
||||
throw IOException("File too large")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emitProgress(Progress.Installing)
|
||||
if (!digest.digest().contentEquals(update.hash.bytes))
|
||||
throw SecurityException("Update has invalid hash")
|
||||
sessionFailure = false
|
||||
} finally {
|
||||
if (sessionFailure) {
|
||||
session.abandon()
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
session.commit(pendingIntent.intentSender)
|
||||
session.close()
|
||||
}
|
||||
|
||||
private suspend fun downloadAndUpdateWrapErrors() {
|
||||
try {
|
||||
downloadAndUpdate()
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, "Update failure", e)
|
||||
emitProgress(Progress.Failure(e))
|
||||
}
|
||||
}
|
||||
|
||||
private class InstallReceiver : BroadcastReceiver() {
|
||||
val sessionId = UUID.randomUUID().toString()
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (sessionId != intent.action)
|
||||
return
|
||||
|
||||
when (val status =
|
||||
intent.getIntExtra(
|
||||
PackageInstaller.EXTRA_STATUS,
|
||||
PackageInstaller.STATUS_FAILURE_INVALID
|
||||
)) {
|
||||
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
||||
val id = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, 0)
|
||||
val userIntervention = IntentCompat.getParcelableExtra(
|
||||
intent,
|
||||
Intent.EXTRA_INTENT,
|
||||
Intent::class.java
|
||||
)!!
|
||||
Application.getCoroutineScope().launch {
|
||||
emitProgress(Progress.NeedsUserIntervention(userIntervention, id))
|
||||
}
|
||||
}
|
||||
|
||||
PackageInstaller.STATUS_SUCCESS -> {
|
||||
Application.getCoroutineScope().launch {
|
||||
emitProgress(Progress.Complete)
|
||||
}
|
||||
context.applicationContext.unregisterReceiver(this)
|
||||
}
|
||||
|
||||
else -> {
|
||||
val id = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, 0)
|
||||
try {
|
||||
context.applicationContext.packageManager.packageInstaller.abandonSession(id)
|
||||
} catch (_: SecurityException) {
|
||||
}
|
||||
val message =
|
||||
intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
|
||||
?: "Installation error $status"
|
||||
Application.getCoroutineScope().launch {
|
||||
val e = Exception(message)
|
||||
Log.e(TAG, "Update failure", e)
|
||||
emitProgress(Progress.Failure(e))
|
||||
}
|
||||
context.applicationContext.unregisterReceiver(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun monitorForUpdates() {
|
||||
if (BuildConfig.IS_GOOGLE_PLAY)
|
||||
return
|
||||
|
||||
updaterScope.launch {
|
||||
if (UserKnobs.updaterNewerVersionSeen.firstOrNull()
|
||||
?.let { Version(it) > CURRENT_VERSION } == true
|
||||
)
|
||||
return@launch
|
||||
|
||||
var waitTime = 15
|
||||
while (true) {
|
||||
try {
|
||||
val update = checkForUpdates() ?: continue
|
||||
if (update.version > CURRENT_VERSION) {
|
||||
Log.i(TAG, "Update available: ${update.version}")
|
||||
UserKnobs.setUpdaterNewerVersionSeen(update.version.toString())
|
||||
return@launch
|
||||
}
|
||||
} catch (_: Throwable) {
|
||||
}
|
||||
delay(waitTime.minutes)
|
||||
waitTime = 45
|
||||
}
|
||||
}
|
||||
|
||||
UserKnobs.updaterNewerVersionSeen.onEach { ver ->
|
||||
if (ver != null && Version(ver) > CURRENT_VERSION && UserKnobs.updaterNewerVersionConsented.firstOrNull()
|
||||
?.let { Version(it) > CURRENT_VERSION } != true
|
||||
)
|
||||
emitProgress(Progress.Available(ver))
|
||||
}.launchIn(Application.getCoroutineScope())
|
||||
|
||||
UserKnobs.updaterNewerVersionConsented.onEach { ver ->
|
||||
if (ver != null && Version(ver) > CURRENT_VERSION)
|
||||
updaterScope.launch {
|
||||
downloadAndUpdateWrapErrors()
|
||||
}
|
||||
}.launchIn(Application.getCoroutineScope())
|
||||
}
|
||||
|
||||
class AppUpdatedReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (BuildConfig.IS_GOOGLE_PLAY)
|
||||
return
|
||||
|
||||
if (intent.action != Intent.ACTION_MY_PACKAGE_REPLACED)
|
||||
return
|
||||
|
||||
val installer = try {
|
||||
val packageName = context.packageName
|
||||
val pm = context.packageManager
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
pm.getInstallSourceInfo(packageName).installingPackageName ?: ""
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
pm.getInstallerPackageName(packageName) ?: ""
|
||||
}
|
||||
} catch (_: Throwable) {
|
||||
""
|
||||
}
|
||||
if (installer != context.packageName)
|
||||
return
|
||||
|
||||
/* TODO: does not work because of restrictions placed on broadcast receivers. */
|
||||
val start = Intent(context, MainActivity::class.java)
|
||||
start.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
start.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
context.startActivity(start)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,8 @@ import android.os.Build
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import java.util.Locale
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.toDuration
|
||||
|
||||
object QuantityFormatter {
|
||||
fun formatBytes(bytes: Long): String {
|
||||
@@ -33,7 +33,7 @@ object QuantityFormatter {
|
||||
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.toDuration(DurationUnit.SECONDS).toString())
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -184,9 +191,9 @@
|
||||
<string name="save">Salvesta</string>
|
||||
<string name="select_all">Vali kõik</string>
|
||||
<string name="settings">Seaded</string>
|
||||
<string name="shell_exit_status_read_error">Kest, shell, ei saa lugeda väljumise olekut</string>
|
||||
<string name="shell_marker_count_error">Kest, shell, ootas 4 markerit, saadi %d</string>
|
||||
<string name="shell_start_error">Kest, shell, ebaõnnestus käivitamisel: %d</string>
|
||||
<string name="shell_exit_status_read_error">Kest ei saa lugeda väljundstaatust</string>
|
||||
<string name="shell_marker_count_error">Kest ootas 4 markerit, saadi %d</string>
|
||||
<string name="shell_start_error">Kesta käivitumine ebaõnnestus: %d</string>
|
||||
<string name="success_application_will_restart">Tegevus õnnestus. Rakendus taaskäivitub…</string>
|
||||
<string name="toggle_all">Vaheta kõik</string>
|
||||
<string name="toggle_error">WireGuard tunneli lülitamine ebaõnnestus: %s</string>
|
||||
@@ -213,6 +220,7 @@
|
||||
<string name="tunnel_create_success">Tunnel \"%s\" lisatud</string>
|
||||
<string name="tunnel_error_already_exists">Tunnel \"%s\" on juba olemas</string>
|
||||
<string name="tunnel_error_invalid_name">Sobimatu nimi</string>
|
||||
<string name="tunnel_list_placeholder">Lisa tunnel alloleva nupu abil</string>
|
||||
<string name="tunnel_name">Tunneli nimi</string>
|
||||
<string name="tunnel_on_error">Tunneli sisselülitamine ebaõnnestus (wgTurnOn tagastas %d)</string>
|
||||
<string name="tunnel_dns_failure">Ei saa lahendada DNS hostinime: \"%s\"</string>
|
||||
|
||||
@@ -66,6 +66,8 @@
|
||||
<string name="bad_config_reason_unknown_section">未知のセクション</string>
|
||||
<string name="bad_config_reason_value_out_of_range">範囲外の値</string>
|
||||
<string name="bad_extension_error">ファイルの拡張子は .conf か .zip です</string>
|
||||
<string name="error_no_qr_found">QRコードが見つかりません</string>
|
||||
<string name="error_qr_checksum">QRコードのチェックサムの確認に失敗しました</string>
|
||||
<string name="cancel">キャンセル</string>
|
||||
<string name="config_delete_error">設定ファイル %s を削除できません</string>
|
||||
<string name="config_exists_error">\"%s\" の定義はすでに存在します</string>
|
||||
@@ -91,7 +93,9 @@
|
||||
<string name="tv_delete">削除するトンネルを選択</string>
|
||||
<string name="tv_select_a_storage_drive">ストレージを選択</string>
|
||||
<string name="tv_no_file_picker">ファイルを参照するにはファイル管理アプリをインストールしてください</string>
|
||||
<string name="tv_add_tunnel_get_started">Add a tunnel to get started</string>
|
||||
<string name="tv_add_tunnel_get_started">トンネルを追加して開始する</string>
|
||||
<string name="donate_title">♥ WireGuard プロジェクトに寄付する</string>
|
||||
<string name="donate_summary">すべての貢献が役立ちます</string>
|
||||
<string name="disable_config_export_title">設定のエクスポートを無効にする</string>
|
||||
<string name="disable_config_export_description">設定のエクスポートを無効にすると、秘密鍵にアクセスされにくくなります</string>
|
||||
<string name="dns_servers">DNS サーバ</string>
|
||||
@@ -101,6 +105,7 @@
|
||||
<string name="error_down">トンネル停止時エラー: %s</string>
|
||||
<string name="error_fetching_apps">アプリ一覧取得エラー: %s</string>
|
||||
<string name="error_root">root 権限を取得して再試行してください</string>
|
||||
<string name="error_prepare">トンネル準備中エラー: %s</string>
|
||||
<string name="error_up">トンネル起動時エラー: %s</string>
|
||||
<string name="exclude_private_ips">プライベート IP アドレスを除外</string>
|
||||
<string name="generate_new_private_key">新しい秘密鍵を生成する</string>
|
||||
@@ -120,6 +125,8 @@
|
||||
<string name="key_length_explanation_base64">: WireGuard base64 鍵は44文字 (32バイト) でなければなりません</string>
|
||||
<string name="key_length_explanation_binary">: WireGuard 鍵は32バイトでなければなりません</string>
|
||||
<string name="key_length_explanation_hex">: WireGuard hex 鍵は64文字 (32バイト) でなければなりません</string>
|
||||
<string name="latest_handshake">直近のハンドシェイク</string>
|
||||
<string name="latest_handshake_ago">%s 前</string>
|
||||
<string name="listen_port">Listen ポート</string>
|
||||
<string name="log_export_error">ログをエクスポートできません: %s</string>
|
||||
<string name="log_export_subject">WireGuard Android ログファイル</string>
|
||||
@@ -198,6 +205,7 @@
|
||||
<string name="tunnel_create_success">トンネル \"%s\" を作成しました</string>
|
||||
<string name="tunnel_error_already_exists">トンネル “%s” はすでに存在します</string>
|
||||
<string name="tunnel_error_invalid_name">不正な名前</string>
|
||||
<string name="tunnel_list_placeholder">下のボタンを使用してトンネルを追加</string>
|
||||
<string name="tunnel_name">トンネル名</string>
|
||||
<string name="tunnel_on_error">トンネルを有効にできません (wgTurnOn が %d を返却)</string>
|
||||
<string name="tunnel_dns_failure">DNSホスト名を解決できませんでした: “%s”</string>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -229,6 +229,13 @@
|
||||
<string name="type_name_go_userspace">Go userspace</string>
|
||||
<string name="type_name_kernel_module">Kernel module</string>
|
||||
<string name="unknown_error">Unknown error</string>
|
||||
<string name="updater_avalable">An application update is available. Please update now.</string>
|
||||
<string name="updater_action">Download & Update</string>
|
||||
<string name="updater_rechecking">Fetching update metadata…</string>
|
||||
<string name="updater_download_progress">Downloading update: %1$s / %2$s (%3$.2f%%)</string>
|
||||
<string name="updater_download_progress_nototal">Downloading update: %s</string>
|
||||
<string name="updater_installing">Installing update…</string>
|
||||
<string name="updater_failure">Update failure: %s. Will retry momentarily…</string>
|
||||
<string name="version_summary">%1$s backend %2$s</string>
|
||||
<string name="version_summary_checking">Checking %s backend version</string>
|
||||
<string name="version_summary_unknown">Unknown %s version</string>
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
buildscript {
|
||||
ext {
|
||||
wireguardVersionCode = 500
|
||||
wireguardVersionName = '1.0.20230427'
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user