mirror of
https://github.com/amnezia-vpn/amneziawg-android.git
synced 2026-07-03 14:05:13 +02:00
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 997edf4e4c | |||
| beb40ad09c | |||
| 4bac284083 | |||
| eae9216fe4 | |||
| dc1860c74d | |||
| 6209d3ee3d | |||
| 33aded09c6 | |||
| f360034f29 | |||
| 01e72064d4 | |||
| 91a75b681d | |||
| 0efdf0cdd7 | |||
| 75efd13556 | |||
| cd812b03cd | |||
| 794a2a1925 | |||
| 75899594c3 | |||
| 4944762d7c | |||
| d6431db248 | |||
| b8f649d1cb | |||
| 9fa8ca8c77 | |||
| 59da677c23 | |||
| 18a06b0a51 | |||
| 5e844bed5e | |||
| 46bf98d7f6 | |||
| 3e9ddd8720 |
+1
-1
@@ -14,5 +14,5 @@ build/
|
||||
*.dex
|
||||
*.iml
|
||||
*.jks
|
||||
keystore.properties
|
||||
gradlew.bat
|
||||
maint/
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
[submodule "tunnel/tools/wireguard-tools"]
|
||||
path = tunnel/tools/wireguard-tools
|
||||
url = https://git.zx2c4.com/wireguard-tools
|
||||
[submodule "tunnel/tools/elf-cleaner"]
|
||||
path = tunnel/tools/elf-cleaner
|
||||
url = https://github.com/termux/termux-elf-cleaner
|
||||
|
||||
+6
-6
@@ -1,20 +1,20 @@
|
||||
buildscript {
|
||||
ext {
|
||||
activityVersion = '1.6.1'
|
||||
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-rc01'
|
||||
coreKtxVersion = '1.10.0'
|
||||
coroutinesVersion = '1.6.4'
|
||||
datastoreVersion = '1.0.0'
|
||||
desugarVersion = '1.1.5'
|
||||
fragmentVersion = '1.5.5'
|
||||
fragmentVersion = '1.5.7'
|
||||
jsr305Version = '3.0.2'
|
||||
junitVersion = '4.13.2'
|
||||
lifecycleRuntimeKtxVersion = '2.6.0'
|
||||
lifecycleRuntimeKtxVersion = '2.6.1'
|
||||
materialComponentsVersion = '1.8.0'
|
||||
preferenceVersion = '1.2.0'
|
||||
zxingEmbeddedVersion = '4.3.0'
|
||||
@@ -25,8 +25,8 @@ buildscript {
|
||||
|
||||
tasks {
|
||||
wrapper {
|
||||
gradleVersion = "8.0.2"
|
||||
distributionSha256Sum = "ff7bf6a86f09b9b2c40bb8f48b25fc19cf2b2664fd1d220cd7ab833ec758d0d7"
|
||||
gradleVersion = "8.1.1"
|
||||
distributionSha256Sum = "e111cb9948407e26351227dabce49822fb88c37ee72f1d1582a69c68af2e702f"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-4
@@ -42,8 +42,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 +59,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
|
||||
|
||||
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
-1
@@ -1,5 +1,5 @@
|
||||
pluginManagement {
|
||||
def agpVersion = '7.4.2'
|
||||
def agpVersion = '8.0.0'
|
||||
def kotlinVersion = "1.8.0"
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
|
||||
+10
-2
@@ -29,17 +29,25 @@ android {
|
||||
}
|
||||
}
|
||||
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}", "-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}"
|
||||
arguments "-DANDROID_PACKAGE_NAME=${groupName}"
|
||||
}
|
||||
}
|
||||
}
|
||||
debug {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DANDROID_PACKAGE_NAME=${groupName}.debug", "-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}"
|
||||
arguments "-DANDROID_PACKAGE_NAME=${groupName}.debug"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.wireguard.crypto.KeyFormatException;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
@@ -125,12 +126,14 @@ public final class GoBackend implements Backend {
|
||||
Key key = null;
|
||||
long rx = 0;
|
||||
long tx = 0;
|
||||
long latestHandshakeMSec = 0;
|
||||
for (final String line : config.split("\\n")) {
|
||||
if (line.startsWith("public_key=")) {
|
||||
if (key != null)
|
||||
stats.add(key, rx, tx);
|
||||
stats.add(key, rx, tx, latestHandshakeMSec);
|
||||
rx = 0;
|
||||
tx = 0;
|
||||
latestHandshakeMSec = 0;
|
||||
try {
|
||||
key = Key.fromHex(line.substring(11));
|
||||
} catch (final KeyFormatException ignored) {
|
||||
@@ -152,10 +155,26 @@ public final class GoBackend implements Backend {
|
||||
} catch (final NumberFormatException ignored) {
|
||||
tx = 0;
|
||||
}
|
||||
} else if (line.startsWith("last_handshake_time_sec=")) {
|
||||
if (key == null)
|
||||
continue;
|
||||
try {
|
||||
latestHandshakeMSec += Long.parseLong(line.substring(24)) * 1000;
|
||||
} catch (final NumberFormatException ignored) {
|
||||
latestHandshakeMSec = 0;
|
||||
}
|
||||
} else if (line.startsWith("last_handshake_time_nsec=")) {
|
||||
if (key == null)
|
||||
continue;
|
||||
try {
|
||||
latestHandshakeMSec += Long.parseLong(line.substring(25)) / 1000000;
|
||||
} catch (final NumberFormatException ignored) {
|
||||
latestHandshakeMSec = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (key != null)
|
||||
stats.add(key, rx, tx);
|
||||
stats.add(key, rx, tx, latestHandshakeMSec);
|
||||
return stats;
|
||||
}
|
||||
|
||||
@@ -324,6 +343,9 @@ public final class GoBackend implements Backend {
|
||||
currentTunnelHandle = -1;
|
||||
currentConfig = null;
|
||||
wgTurnOff(handleToClose);
|
||||
try {
|
||||
vpnService.get(0, TimeUnit.NANOSECONDS).stopSelf();
|
||||
} catch (final TimeoutException ignored) { }
|
||||
}
|
||||
|
||||
tunnel.onStateChange(state);
|
||||
|
||||
@@ -6,36 +6,65 @@
|
||||
package com.wireguard.android.backend;
|
||||
|
||||
import android.os.SystemClock;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.wireguard.crypto.Key;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Class representing transfer statistics for a {@link Tunnel} instance.
|
||||
*/
|
||||
@NonNullForAll
|
||||
public class Statistics {
|
||||
private final Map<Key, Pair<Long, Long>> peerBytes = new HashMap<>();
|
||||
|
||||
// TODO: switch to Java Record class once R8 supports desugaring those.
|
||||
public final class PeerStats {
|
||||
public final long rxBytes, txBytes, latestHandshakeEpochMillis;
|
||||
|
||||
PeerStats(final long rxBytes, final long txBytes, final long latestHandshakeEpochMillis) {
|
||||
this.rxBytes = rxBytes;
|
||||
this.txBytes = txBytes;
|
||||
this.latestHandshakeEpochMillis = latestHandshakeEpochMillis;
|
||||
}
|
||||
|
||||
@Override public boolean equals(final Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
final PeerStats stats = (PeerStats) o;
|
||||
return rxBytes == stats.rxBytes && txBytes == stats.txBytes && latestHandshakeEpochMillis == stats.latestHandshakeEpochMillis;
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
return Objects.hash(rxBytes, txBytes, latestHandshakeEpochMillis);
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<Key, PeerStats> stats = new HashMap<>();
|
||||
private long lastTouched = SystemClock.elapsedRealtime();
|
||||
|
||||
Statistics() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a peer and its current data usage to the internal map.
|
||||
* Add a peer and its current stats to the internal map.
|
||||
*
|
||||
* @param key A WireGuard public key bound to a particular peer
|
||||
* @param rx The received traffic for the {@link com.wireguard.config.Peer} referenced by
|
||||
* the provided {@link Key}. This value is in bytes
|
||||
* @param tx The transmitted traffic for the {@link com.wireguard.config.Peer} referenced by
|
||||
* the provided {@link Key}. This value is in bytes.
|
||||
* @param key A WireGuard public key bound to a particular peer
|
||||
* @param rxBytes The received traffic for the {@link com.wireguard.config.Peer} referenced by
|
||||
* the provided {@link Key}. This value is in bytes
|
||||
* @param txBytes The transmitted traffic for the {@link com.wireguard.config.Peer} referenced by
|
||||
* the provided {@link Key}. This value is in bytes.
|
||||
* @param latestHandshake The timestamp of the latest handshake for the {@link com.wireguard.config.Peer}
|
||||
* referenced by the provided {@link Key}. The value is in epoch milliseconds.
|
||||
*/
|
||||
void add(final Key key, final long rx, final long tx) {
|
||||
peerBytes.put(key, Pair.create(rx, tx));
|
||||
void add(final Key key, final long rxBytes, final long txBytes, final long latestHandshake) {
|
||||
stats.put(key, new PeerStats(rxBytes, txBytes, latestHandshake));
|
||||
lastTouched = SystemClock.elapsedRealtime();
|
||||
}
|
||||
|
||||
@@ -49,31 +78,14 @@ public class Statistics {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the received traffic (in bytes) for the {@link com.wireguard.config.Peer} referenced by
|
||||
* the provided {@link Key}
|
||||
* Get the statistics for the {@link com.wireguard.config.Peer} referenced by the provided {@link Key}
|
||||
*
|
||||
* @param peer A {@link Key} representing a {@link com.wireguard.config.Peer}.
|
||||
* @return a long representing the number of bytes received by this peer.
|
||||
* @return a {@link PeerStats} representing various statistics about this peer.
|
||||
*/
|
||||
public long peerRx(final Key peer) {
|
||||
final Pair<Long, Long> rxTx = peerBytes.get(peer);
|
||||
if (rxTx == null)
|
||||
return 0;
|
||||
return rxTx.first;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the transmitted traffic (in bytes) for the {@link com.wireguard.config.Peer} referenced by
|
||||
* the provided {@link Key}
|
||||
*
|
||||
* @param peer A {@link Key} representing a {@link com.wireguard.config.Peer}.
|
||||
* @return a long representing the number of bytes transmitted by this peer.
|
||||
*/
|
||||
public long peerTx(final Key peer) {
|
||||
final Pair<Long, Long> rxTx = peerBytes.get(peer);
|
||||
if (rxTx == null)
|
||||
return 0;
|
||||
return rxTx.second;
|
||||
@Nullable
|
||||
public PeerStats peer(final Key peer) {
|
||||
return this.stats.get(peer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,7 +95,7 @@ public class Statistics {
|
||||
* {@link com.wireguard.config.Peer}s
|
||||
*/
|
||||
public Key[] peers() {
|
||||
return peerBytes.keySet().toArray(new Key[0]);
|
||||
return stats.keySet().toArray(new Key[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,8 +105,8 @@ public class Statistics {
|
||||
*/
|
||||
public long totalRx() {
|
||||
long rx = 0;
|
||||
for (final Pair<Long, Long> val : peerBytes.values()) {
|
||||
rx += val.first;
|
||||
for (final PeerStats val : stats.values()) {
|
||||
rx += val.rxBytes;
|
||||
}
|
||||
return rx;
|
||||
}
|
||||
@@ -106,8 +118,8 @@ public class Statistics {
|
||||
*/
|
||||
public long totalTx() {
|
||||
long tx = 0;
|
||||
for (final Pair<Long, Long> val : peerBytes.values()) {
|
||||
tx += val.second;
|
||||
for (final PeerStats val : stats.values()) {
|
||||
tx += val.txBytes;
|
||||
}
|
||||
return tx;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.wireguard.util.NonNullForAll;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -83,17 +84,17 @@ public final class WgQuickBackend implements Backend {
|
||||
final Statistics stats = new Statistics();
|
||||
final Collection<String> output = new ArrayList<>();
|
||||
try {
|
||||
if (rootShell.run(output, String.format("wg show '%s' transfer", tunnel.getName())) != 0)
|
||||
if (rootShell.run(output, String.format("wg show '%s' dump", tunnel.getName())) != 0)
|
||||
return stats;
|
||||
} catch (final Exception ignored) {
|
||||
return stats;
|
||||
}
|
||||
for (final String line : output) {
|
||||
final String[] parts = line.split("\\t");
|
||||
if (parts.length != 3)
|
||||
if (parts.length != 8)
|
||||
continue;
|
||||
try {
|
||||
stats.add(Key.fromBase64(parts[0]), Long.parseLong(parts[1]), Long.parseLong(parts[2]));
|
||||
stats.add(Key.fromBase64(parts[0]), Long.parseLong(parts[5]), Long.parseLong(parts[6]), Long.parseLong(parts[4]) * 1000);
|
||||
} catch (final Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
@@ -28,5 +29,16 @@ add_custom_target(libwg-go.so WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/lib
|
||||
DESTDIR=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
|
||||
BUILDDIR=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/../generated-src
|
||||
)
|
||||
# Hack to make it actually build as part of the default target
|
||||
add_dependencies(libwg.so libwg-go.so)
|
||||
|
||||
# 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 cc
|
||||
-O2 -DPACKAGE_NAME="elf-cleaner" -DPACKAGE_VERSION="" -DCOPYRIGHT=""
|
||||
-o "${CMAKE_CURRENT_BINARY_DIR}/elf-cleaner" ${ELF_CLEANER_SOURCES}
|
||||
)
|
||||
add_custom_command(TARGET libwg.so POST_BUILD VERBATIM COMMAND "${CMAKE_CURRENT_BINARY_DIR}/elf-cleaner"
|
||||
--api-level "${ANDROID_NATIVE_API_LEVEL}" "$<TARGET_FILE:libwg.so>")
|
||||
add_dependencies(libwg.so elf-cleaner)
|
||||
add_custom_command(TARGET libwg-quick.so POST_BUILD VERBATIM COMMAND "${CMAKE_CURRENT_BINARY_DIR}/elf-cleaner"
|
||||
--api-level "${ANDROID_NATIVE_API_LEVEL}" "$<TARGET_FILE:libwg-quick.so>")
|
||||
add_dependencies(libwg-quick.so elf-cleaner)
|
||||
|
||||
Submodule
+1
Submodule tunnel/tools/elf-cleaner added at 7efc050906
@@ -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:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
From 729c58cb1c0496497dac6de3d0bf540f6149618f Mon Sep 17 00:00:00 2001
|
||||
From 61f3ae8298d1c503cbc31539e0f3a73446c7db9d Mon Sep 17 00:00:00 2001
|
||||
From: "Jason A. Donenfeld" <Jason@zx2c4.com>
|
||||
Date: Tue, 21 Mar 2023 15:33:56 +0100
|
||||
Subject: [PATCH] [release-branch.go1.20] runtime: use CLOCK_BOOTTIME in
|
||||
@@ -22,11 +22,11 @@ Change-Id: I7b2a6ca0c5bc5fce57ec0eeafe7b68270b429321
|
||||
src/runtime/sys_linux_amd64.s | 2 +-
|
||||
src/runtime/sys_linux_arm.s | 4 ++--
|
||||
src/runtime/sys_linux_arm64.s | 4 ++--
|
||||
src/runtime/sys_linux_mips64x.s | 2 +-
|
||||
src/runtime/sys_linux_mips64x.s | 4 ++--
|
||||
src/runtime/sys_linux_mipsx.s | 2 +-
|
||||
src/runtime/sys_linux_ppc64x.s | 2 +-
|
||||
src/runtime/sys_linux_s390x.s | 2 +-
|
||||
8 files changed, 11 insertions(+), 11 deletions(-)
|
||||
8 files changed, 12 insertions(+), 12 deletions(-)
|
||||
|
||||
diff --git a/src/runtime/sys_linux_386.s b/src/runtime/sys_linux_386.s
|
||||
index 12a294153d..17e3524b40 100644
|
||||
@@ -106,7 +106,7 @@ index 38ff6ac330..6b819c5441 100644
|
||||
CBZ R2, fallback
|
||||
|
||||
diff --git a/src/runtime/sys_linux_mips64x.s b/src/runtime/sys_linux_mips64x.s
|
||||
index 47f2da524d..6c1a9a2801 100644
|
||||
index 47f2da524d..a8b387f193 100644
|
||||
--- a/src/runtime/sys_linux_mips64x.s
|
||||
+++ b/src/runtime/sys_linux_mips64x.s
|
||||
@@ -326,7 +326,7 @@ noswitch:
|
||||
@@ -118,6 +118,15 @@ index 47f2da524d..6c1a9a2801 100644
|
||||
MOVV $0(R29), R5
|
||||
|
||||
MOVV runtime·vdsoClockgettimeSym(SB), R25
|
||||
@@ -336,7 +336,7 @@ noswitch:
|
||||
// see walltime for detail
|
||||
BEQ R2, R0, finish
|
||||
MOVV R0, runtime·vdsoClockgettimeSym(SB)
|
||||
- MOVW $1, R4 // CLOCK_MONOTONIC
|
||||
+ MOVW $7, R4 // CLOCK_BOOTTIME
|
||||
MOVV $0(R29), R5
|
||||
JMP fallback
|
||||
|
||||
diff --git a/src/runtime/sys_linux_mipsx.s b/src/runtime/sys_linux_mipsx.s
|
||||
index 5e6b6c1504..7f5fd2a80e 100644
|
||||
--- a/src/runtime/sys_linux_mipsx.s
|
||||
@@ -158,5 +167,5 @@ index 1448670b91..7d2ee3231c 100644
|
||||
MOVD R15, R7 // Backup stack pointer
|
||||
|
||||
--
|
||||
2.40.0
|
||||
2.17.1
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+2
-22
@@ -9,10 +9,6 @@ plugins {
|
||||
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 {
|
||||
@@ -34,27 +30,11 @@ android {
|
||||
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 {
|
||||
all {
|
||||
buildConfigField('boolean', 'IS_GOOGLE_PLAY', (findProperty('build.google_play') == 'true').toString())
|
||||
}
|
||||
release {
|
||||
if (keystorePropertiesFile.exists()) signingConfig signingConfigs.release
|
||||
minifyEnabled true
|
||||
proguardFiles "proguard-android-optimize.txt", "proguard-rules.pro"
|
||||
shrinkResources true
|
||||
proguardFiles "proguard-android-optimize.txt"
|
||||
packagingOptions {
|
||||
exclude "DebugProbesKt.bin"
|
||||
exclude "kotlin-tooling-metadata.json"
|
||||
|
||||
@@ -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
|
||||
@@ -17,8 +17,6 @@ import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||
import com.google.android.material.color.DynamicColors
|
||||
import com.google.android.material.color.DynamicColorsOptions
|
||||
import com.wireguard.android.activity.TvMainActivity
|
||||
import com.wireguard.android.backend.Backend
|
||||
import com.wireguard.android.backend.GoBackend
|
||||
import com.wireguard.android.backend.WgQuickBackend
|
||||
@@ -37,6 +35,7 @@ import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.Locale
|
||||
|
||||
@@ -59,10 +58,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 {
|
||||
@@ -89,17 +84,24 @@ class Application : android.app.Application() {
|
||||
override fun onCreate() {
|
||||
Log.i(TAG, USER_AGENT)
|
||||
super.onCreate()
|
||||
DynamicColors.applyToActivitiesIfAvailable(this,
|
||||
// TODO: Remove this second argument once the TV theme has a proper M3 color palette.
|
||||
DynamicColorsOptions.Builder().setPrecondition { activity, _ -> activity !is TvMainActivity }.build())
|
||||
DynamicColors.applyToActivitiesIfAvailable(this)
|
||||
rootShell = RootShell(applicationContext)
|
||||
toolsInstaller = ToolsInstaller(applicationContext, rootShell)
|
||||
preferencesDataStore = PreferenceDataStoreFactory.create { applicationContext.preferencesDataStoreFile("settings") }
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
coroutineScope.launch {
|
||||
AppCompatDelegate.setDefaultNightMode(
|
||||
if (UserKnobs.darkTheme.first()) AppCompatDelegate.MODE_NIGHT_YES else AppCompatDelegate.MODE_NIGHT_NO)
|
||||
runBlocking {
|
||||
AppCompatDelegate.setDefaultNightMode(if (UserKnobs.darkTheme.first()) AppCompatDelegate.MODE_NIGHT_YES else AppCompatDelegate.MODE_NIGHT_NO)
|
||||
}
|
||||
UserKnobs.darkTheme.onEach {
|
||||
val newMode = if (it) {
|
||||
AppCompatDelegate.MODE_NIGHT_YES
|
||||
} else {
|
||||
AppCompatDelegate.MODE_NIGHT_NO
|
||||
}
|
||||
if (AppCompatDelegate.getDefaultNightMode() != newMode) {
|
||||
AppCompatDelegate.setDefaultNightMode(newMode)
|
||||
}
|
||||
}.launchIn(coroutineScope)
|
||||
} else {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||
}
|
||||
@@ -113,6 +115,11 @@ class Application : android.app.Application() {
|
||||
Log.e(TAG, Log.getStackTraceString(e))
|
||||
}
|
||||
}
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
StrictMode.setVmPolicy(VmPolicy.Builder().detectAll().penaltyLog().build())
|
||||
StrictMode.setThreadPolicy(ThreadPolicy.Builder().detectAll().penaltyLog().build())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTerminate() {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package com.wireguard.android.activity
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.databinding.CallbackRegistry
|
||||
import androidx.databinding.CallbackRegistry.NotifierCallback
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
@@ -15,15 +16,21 @@ import kotlinx.coroutines.launch
|
||||
/**
|
||||
* Base class for activities that need to remember the currently-selected tunnel.
|
||||
*/
|
||||
abstract class BaseActivity : ThemeChangeAwareActivity() {
|
||||
abstract class BaseActivity : AppCompatActivity() {
|
||||
private val selectionChangeRegistry = SelectionChangeRegistry()
|
||||
private var created = false
|
||||
var selectedTunnel: ObservableTunnel? = null
|
||||
set(value) {
|
||||
val oldTunnel = field
|
||||
if (oldTunnel == value) return
|
||||
field = value
|
||||
onSelectedTunnelChanged(oldTunnel, value)
|
||||
selectionChangeRegistry.notifyCallbacks(oldTunnel, 0, value)
|
||||
if (created) {
|
||||
if (!onSelectedTunnelChanged(oldTunnel, value)) {
|
||||
field = oldTunnel
|
||||
} else {
|
||||
selectionChangeRegistry.notifyCallbacks(oldTunnel, 0, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addOnSelectedTunnelChangedListener(listener: OnSelectedTunnelChangedListener) {
|
||||
@@ -39,10 +46,17 @@ abstract class BaseActivity : ThemeChangeAwareActivity() {
|
||||
intent != null -> intent.getStringExtra(KEY_SELECTED_TUNNEL)
|
||||
else -> null
|
||||
}
|
||||
if (savedTunnelName != null)
|
||||
if (savedTunnelName != null) {
|
||||
lifecycleScope.launch {
|
||||
selectedTunnel = Application.getTunnelManager().getTunnels()[savedTunnelName]
|
||||
val tunnel = Application.getTunnelManager().getTunnels()[savedTunnelName]
|
||||
if (tunnel == null)
|
||||
created = true
|
||||
selectedTunnel = tunnel
|
||||
created = true
|
||||
}
|
||||
} else {
|
||||
created = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
@@ -50,7 +64,7 @@ abstract class BaseActivity : ThemeChangeAwareActivity() {
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
protected abstract fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?, newTunnel: ObservableTunnel?)
|
||||
protected abstract fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?, newTunnel: ObservableTunnel?): Boolean
|
||||
|
||||
fun removeOnSelectedTunnelChangedListener(
|
||||
listener: OnSelectedTunnelChangedListener) {
|
||||
|
||||
@@ -27,6 +27,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.collection.CircularArray
|
||||
import androidx.core.app.ShareCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
@@ -62,8 +63,8 @@ import java.util.regex.Pattern
|
||||
class LogViewerActivity : AppCompatActivity() {
|
||||
private lateinit var binding: LogViewerActivityBinding
|
||||
private lateinit var logAdapter: LogEntryAdapter
|
||||
private var logLines = arrayListOf<LogLine>()
|
||||
private var rawLogLines = StringBuffer()
|
||||
private var logLines = CircularArray<LogLine>()
|
||||
private var rawLogLines = CircularArray<String>()
|
||||
private var recyclerView: RecyclerView? = null
|
||||
private var saveButton: MenuItem? = null
|
||||
private val year by lazy {
|
||||
@@ -113,7 +114,7 @@ class LogViewerActivity : AppCompatActivity() {
|
||||
binding.shareFab.setOnClickListener {
|
||||
revokeLastUri()
|
||||
val key = KeyPair().privateKey.toHex()
|
||||
LOGS[key] = rawLogLines.toString().toByteArray(Charsets.UTF_8)
|
||||
LOGS[key] = rawLogBytes()
|
||||
lastUri = Uri.parse("content://${BuildConfig.APPLICATION_ID}.exported-log/$key")
|
||||
val shareIntent = ShareCompat.IntentBuilder(this)
|
||||
.setType("text/plain")
|
||||
@@ -150,13 +151,22 @@ class LogViewerActivity : AppCompatActivity() {
|
||||
|
||||
private val downloadsFileSaver = DownloadsFileSaver(this)
|
||||
|
||||
private fun rawLogBytes() : ByteArray {
|
||||
val builder = StringBuilder()
|
||||
for (i in 0 until rawLogLines.size()) {
|
||||
builder.append(rawLogLines[i])
|
||||
builder.append('\n')
|
||||
}
|
||||
return builder.toString().toByteArray(Charsets.UTF_8)
|
||||
}
|
||||
|
||||
private suspend fun saveLog() {
|
||||
var exception: Throwable? = null
|
||||
var outputFile: DownloadsFileSaver.DownloadsFile? = null
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
outputFile = downloadsFileSaver.save("wireguard-log.txt", "text/plain", true)
|
||||
outputFile?.outputStream?.write(rawLogLines.toString().toByteArray(Charsets.UTF_8))
|
||||
outputFile?.outputStream?.write(rawLogBytes())
|
||||
} catch (e: Throwable) {
|
||||
outputFile?.delete()
|
||||
exception = e
|
||||
@@ -191,24 +201,27 @@ 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 16) - 1
|
||||
val MAX_BUFFERED_LINES = (1 shl 14) - 1
|
||||
|
||||
while (true) {
|
||||
val line = stdout.readLine() ?: break
|
||||
rawLogLines.append(line)
|
||||
rawLogLines.append('\n')
|
||||
if (rawLogLines.size() >= MAX_LINES)
|
||||
rawLogLines.popFirst()
|
||||
rawLogLines.addLast(line)
|
||||
val logLine = parseLine(line)
|
||||
if (logLine != null) {
|
||||
bufferedLogLines.add(logLine)
|
||||
} else {
|
||||
if (bufferedLogLines.isNotEmpty()) {
|
||||
bufferedLogLines.last().msg += "\n$line"
|
||||
} else if (logLines.isNotEmpty()) {
|
||||
logLines.last().msg += "\n$line"
|
||||
} else if (!logLines.isEmpty) {
|
||||
logLines[logLines.size() - 1].msg += "\n$line"
|
||||
priorModified = true
|
||||
}
|
||||
}
|
||||
val timeNow = System.nanoTime()
|
||||
if ((timeNow - timeLastNotify) < timeout && stdout.ready())
|
||||
if (bufferedLogLines.size < MAX_BUFFERED_LINES && (timeNow - timeLastNotify) < timeout && stdout.ready())
|
||||
continue
|
||||
timeout = 1000000000L * 5 / 2 // Increase the timeout after the initial view has something in it.
|
||||
timeLastNotify = timeNow
|
||||
@@ -219,13 +232,23 @@ class LogViewerActivity : AppCompatActivity() {
|
||||
logAdapter.notifyItemChanged(posStart - 1)
|
||||
priorModified = false
|
||||
}
|
||||
logLines.addAll(bufferedLogLines)
|
||||
val fullLen = logLines.size() + bufferedLogLines.size
|
||||
if (fullLen >= MAX_LINES) {
|
||||
val numToRemove = fullLen - MAX_LINES + 1
|
||||
logLines.removeFromStart(numToRemove)
|
||||
logAdapter.notifyItemRangeRemoved(0, numToRemove)
|
||||
posStart -= numToRemove
|
||||
|
||||
}
|
||||
for (bufferedLine in bufferedLogLines) {
|
||||
logLines.addLast(bufferedLine)
|
||||
}
|
||||
bufferedLogLines.clear()
|
||||
logAdapter.notifyItemRangeInserted(posStart, logLines.size - posStart)
|
||||
posStart = logLines.size
|
||||
logAdapter.notifyItemRangeInserted(posStart, logLines.size() - posStart)
|
||||
posStart = logLines.size()
|
||||
|
||||
if (isScrolledToBottomAlready) {
|
||||
recyclerView?.scrollToPosition(logLines.size - 1)
|
||||
recyclerView?.scrollToPosition(logLines.size() - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -279,7 +302,7 @@ class LogViewerActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount() = logLines.size
|
||||
override fun getItemCount() = logLines.size()
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
|
||||
@@ -96,13 +96,17 @@ class MainActivity : BaseActivity(), FragmentManager.OnBackStackChangedListener
|
||||
}
|
||||
|
||||
override fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?,
|
||||
newTunnel: ObservableTunnel?) {
|
||||
newTunnel: ObservableTunnel?): Boolean {
|
||||
val fragmentManager = supportFragmentManager
|
||||
if (fragmentManager.isStateSaved) {
|
||||
return false
|
||||
}
|
||||
|
||||
val backStackEntries = fragmentManager.backStackEntryCount
|
||||
if (newTunnel == null) {
|
||||
// Clear everything off the back stack (all editors and detail fragments).
|
||||
fragmentManager.popBackStackImmediate(0, FragmentManager.POP_BACK_STACK_INCLUSIVE)
|
||||
return
|
||||
return true
|
||||
}
|
||||
if (backStackEntries == 2) {
|
||||
// Pop the editor off the back stack to reveal the detail fragment. Use the immediate
|
||||
@@ -116,5 +120,6 @@ class MainActivity : BaseActivity(), FragmentManager.OnBackStackChangedListener
|
||||
addToBackStack(null)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.commit
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.Preference
|
||||
@@ -24,7 +25,7 @@ import kotlinx.coroutines.withContext
|
||||
/**
|
||||
* Interface for changing application-global persistent settings.
|
||||
*/
|
||||
class SettingsActivity : ThemeChangeAwareActivity() {
|
||||
class SettingsActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (supportFragmentManager.findFragmentById(android.R.id.content) == null) {
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.activity
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.wireguard.android.util.UserKnobs
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
abstract class ThemeChangeAwareActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
UserKnobs.darkTheme.onEach {
|
||||
val newMode = if (it) {
|
||||
AppCompatDelegate.MODE_NIGHT_YES
|
||||
} else {
|
||||
AppCompatDelegate.MODE_NIGHT_NO
|
||||
}
|
||||
if (AppCompatDelegate.getDefaultNightMode() != newMode) {
|
||||
AppCompatDelegate.setDefaultNightMode(newMode)
|
||||
recreate()
|
||||
}
|
||||
}.launchIn(lifecycleScope)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,8 @@ class TunnelCreatorActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?, newTunnel: ObservableTunnel?) {
|
||||
override fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?, newTunnel: ObservableTunnel?): Boolean {
|
||||
finish()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
package com.wireguard.android.activity
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
@@ -19,6 +22,7 @@ import android.widget.Toast
|
||||
import androidx.activity.addCallback
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.view.forEach
|
||||
@@ -29,6 +33,7 @@ import androidx.databinding.ObservableField
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.backend.GoBackend
|
||||
@@ -43,6 +48,8 @@ import com.wireguard.android.model.ObservableTunnel
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import com.wireguard.android.util.QuantityFormatter
|
||||
import com.wireguard.android.util.TunnelImporter
|
||||
import com.wireguard.android.util.UserKnobs
|
||||
import com.wireguard.android.util.applicationScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -50,7 +57,27 @@ import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
class TvMainActivity : AppCompatActivity() {
|
||||
private val tunnelFileImportResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { data ->
|
||||
private val tunnelFileImportResultLauncher = registerForActivityResult(object : ActivityResultContracts.GetContent() {
|
||||
override fun createIntent(context: Context, input: String): Intent {
|
||||
val intent = super.createIntent(context, input)
|
||||
|
||||
/* AndroidTV now comes with stubs that do nothing but display a Toast less helpful than
|
||||
* what we can do, so detect this and throw an exception that we can catch later. */
|
||||
val activitiesToResolveIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
context.packageManager.queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()))
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
context.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
|
||||
}
|
||||
if (activitiesToResolveIntent.all {
|
||||
val name = it.activityInfo.packageName
|
||||
name.startsWith("com.google.android.tv.frameworkpackagestubs") || name.startsWith("com.android.tv.frameworkpackagestubs")
|
||||
}) {
|
||||
throw ActivityNotFoundException()
|
||||
}
|
||||
return intent
|
||||
}
|
||||
}) { data ->
|
||||
if (data == null) return@registerForActivityResult
|
||||
lifecycleScope.launch {
|
||||
TunnelImporter.importTunnel(contentResolver, data) {
|
||||
@@ -86,6 +113,14 @@ class TvMainActivity : AppCompatActivity() {
|
||||
private val filesRoot = ObservableField("")
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
if (AppCompatDelegate.getDefaultNightMode() != AppCompatDelegate.MODE_NIGHT_YES) {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
applicationScope.launch {
|
||||
UserKnobs.setDarkTheme(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = TvActivityBinding.inflate(layoutInflater)
|
||||
lifecycleScope.launch {
|
||||
@@ -176,7 +211,12 @@ class TvMainActivity : AppCompatActivity() {
|
||||
try {
|
||||
tunnelFileImportResultLauncher.launch("*/*")
|
||||
} catch (_: Throwable) {
|
||||
Toast.makeText(this@TvMainActivity, getString(R.string.tv_no_file_picker), Toast.LENGTH_LONG).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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.wireguard.android.R
|
||||
import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler
|
||||
import com.wireguard.android.widget.ToggleSwitch
|
||||
import com.wireguard.android.widget.ToggleSwitch.OnBeforeCheckedChangeListener
|
||||
import com.wireguard.android.widget.TvCardView
|
||||
import com.wireguard.config.Attribute
|
||||
import com.wireguard.config.InetNetwork
|
||||
import java.net.InetAddress
|
||||
@@ -168,4 +169,16 @@ object BindingAdapters {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@BindingAdapter("isUp")
|
||||
fun setIsUp(card: TvCardView, up: Boolean) {
|
||||
card.isUp = up
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@BindingAdapter("isDeleting")
|
||||
fun setIsDeleting(card: TvCardView, deleting: Boolean) {
|
||||
card.isDeleting = deleting
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,8 +63,8 @@ class AppListDialogFragment : DialogFragment() {
|
||||
withContext(Dispatchers.Main.immediate) {
|
||||
appData.clear()
|
||||
appData.addAll(applicationData)
|
||||
setButtonText()
|
||||
}
|
||||
setButtonText()
|
||||
} catch (e: Throwable) {
|
||||
withContext(Dispatchers.Main.immediate) {
|
||||
val error = ErrorMessages[e]
|
||||
|
||||
@@ -67,12 +67,20 @@ abstract class BaseFragment : Fragment(), OnSelectedTunnelChangedListener {
|
||||
val activity = activity ?: return
|
||||
activity.lifecycleScope.launch {
|
||||
if (Application.getBackend() is GoBackend) {
|
||||
val intent = GoBackend.VpnService.prepare(activity)
|
||||
if (intent != null) {
|
||||
pendingTunnel = tunnel
|
||||
pendingTunnelUp = checked
|
||||
permissionActivityResultLauncher.launch(intent)
|
||||
return@launch
|
||||
try {
|
||||
val intent = GoBackend.VpnService.prepare(activity)
|
||||
if (intent != null) {
|
||||
pendingTunnel = tunnel
|
||||
pendingTunnelUp = checked
|
||||
permissionActivityResultLauncher.launch(intent)
|
||||
return@launch
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
val message = activity.getString(R.string.error_prepare, ErrorMessages[e])
|
||||
Snackbar.make(view, message, Snackbar.LENGTH_LONG)
|
||||
.setAnchorView(view.findViewById(R.id.create_fab))
|
||||
.show()
|
||||
Log.e(TAG, message, e)
|
||||
}
|
||||
}
|
||||
setTunnelStateWithPermissionsResult(tunnel, checked)
|
||||
|
||||
@@ -112,16 +112,25 @@ class TunnelDetailFragment : BaseFragment(), MenuProvider {
|
||||
val peer: TunnelDetailPeerBinding = DataBindingUtil.getBinding(binding.peersLayout.getChildAt(i))
|
||||
?: continue
|
||||
val publicKey = peer.item!!.publicKey
|
||||
val rx = statistics.peerRx(publicKey)
|
||||
val tx = statistics.peerTx(publicKey)
|
||||
if (rx == 0L && tx == 0L) {
|
||||
val peerStats = statistics.peer(publicKey)
|
||||
if (peerStats == null || (peerStats.rxBytes == 0L && peerStats.txBytes == 0L)) {
|
||||
peer.transferLabel.visibility = View.GONE
|
||||
peer.transferText.visibility = View.GONE
|
||||
continue
|
||||
} else {
|
||||
peer.transferText.text = getString(R.string.transfer_rx_tx,
|
||||
QuantityFormatter.formatBytes(peerStats.rxBytes),
|
||||
QuantityFormatter.formatBytes(peerStats.txBytes))
|
||||
peer.transferLabel.visibility = View.VISIBLE
|
||||
peer.transferText.visibility = View.VISIBLE
|
||||
}
|
||||
if (peerStats == null || peerStats.latestHandshakeEpochMillis == 0L) {
|
||||
peer.latestHandshakeLabel.visibility = View.GONE
|
||||
peer.latestHandshakeText.visibility = View.GONE
|
||||
} else {
|
||||
peer.latestHandshakeText.text = QuantityFormatter.formatEpochAgo(peerStats.latestHandshakeEpochMillis)
|
||||
peer.latestHandshakeLabel.visibility = View.VISIBLE
|
||||
peer.latestHandshakeText.visibility = View.VISIBLE
|
||||
}
|
||||
peer.transferText.text = getString(R.string.transfer_rx_tx, QuantityFormatter.formatBytes(rx), QuantityFormatter.formatBytes(tx))
|
||||
peer.transferLabel.visibility = View.VISIBLE
|
||||
peer.transferText.visibility = View.VISIBLE
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
for (i in 0 until binding.peersLayout.childCount) {
|
||||
@@ -129,6 +138,8 @@ class TunnelDetailFragment : BaseFragment(), MenuProvider {
|
||||
?: continue
|
||||
peer.transferLabel.visibility = View.GONE
|
||||
peer.transferText.visibility = View.GONE
|
||||
peer.latestHandshakeLabel.visibility = View.GONE
|
||||
peer.latestHandshakeText.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ class TunnelListFragment : BaseFragment() {
|
||||
}
|
||||
}
|
||||
}
|
||||
bottomSheet.show(childFragmentManager, "BOTTOM_SHEET")
|
||||
bottomSheet.showNow(childFragmentManager, "BOTTOM_SHEET")
|
||||
}
|
||||
executePendingBindings()
|
||||
}
|
||||
|
||||
@@ -5,15 +5,16 @@
|
||||
|
||||
package com.wireguard.android.preference
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.ActivityNotFoundException
|
||||
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.wireguard.android.BuildConfig
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
|
||||
class DonatePreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
|
||||
override fun getSummary() = context.getString(R.string.donate_summary)
|
||||
@@ -21,18 +22,34 @@ class DonatePreference(context: Context, attrs: AttributeSet?) : Preference(cont
|
||||
override fun getTitle() = context.getString(R.string.donate_title)
|
||||
|
||||
override fun onClick() {
|
||||
if (BuildConfig.IS_GOOGLE_PLAY) {
|
||||
AlertDialog.Builder(context)
|
||||
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") {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.donate_title)
|
||||
.setMessage(R.string.donate_google_play_disappointment)
|
||||
.show()
|
||||
return
|
||||
}
|
||||
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = Uri.parse("https://www.wireguard.com/donations/")
|
||||
try {
|
||||
context.startActivity(intent)
|
||||
} catch (ignored: ActivityNotFoundException) {
|
||||
} catch (e: Throwable) {
|
||||
Toast.makeText(context, ErrorMessages[e], Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
*/
|
||||
package com.wireguard.android.preference
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.util.AttributeSet
|
||||
import android.widget.Toast
|
||||
import androidx.preference.Preference
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.BuildConfig
|
||||
@@ -16,6 +16,7 @@ import com.wireguard.android.R
|
||||
import com.wireguard.android.backend.Backend
|
||||
import com.wireguard.android.backend.GoBackend
|
||||
import com.wireguard.android.backend.WgQuickBackend
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import com.wireguard.android.util.lifecycleScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -33,7 +34,8 @@ class VersionPreference(context: Context, attrs: AttributeSet?) : Preference(con
|
||||
intent.data = Uri.parse("https://www.wireguard.com/")
|
||||
try {
|
||||
context.startActivity(intent)
|
||||
} catch (_: ActivityNotFoundException) {
|
||||
} catch (e: Throwable) {
|
||||
Toast.makeText(context, ErrorMessages[e], Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -92,8 +92,8 @@ object ErrorMessages {
|
||||
rootCause is ChecksumException -> {
|
||||
resources.getString(R.string.error_qr_checksum)
|
||||
}
|
||||
rootCause.message != null -> {
|
||||
rootCause.message!!
|
||||
rootCause.localizedMessage != null -> {
|
||||
rootCause.localizedMessage!!
|
||||
}
|
||||
else -> {
|
||||
val errorType = rootCause.javaClass.simpleName
|
||||
@@ -109,7 +109,7 @@ object ErrorMessages {
|
||||
if (kfe!!.type == KeyFormatException.Type.LENGTH) return resources.getString(KFE_FORMAT_MAP.getValue(kfe.format))
|
||||
} else if (bce.cause is ParseException) {
|
||||
val pe = bce.cause as ParseException?
|
||||
if (pe!!.message != null) return ": ${pe.message}"
|
||||
if (pe!!.localizedMessage != null) return ": ${pe.localizedMessage}"
|
||||
} else if (bce.location == BadConfigException.Location.LISTEN_PORT) {
|
||||
return resources.getString(R.string.bad_config_explanation_udp_port)
|
||||
} else if (bce.location == BadConfigException.Location.MTU) {
|
||||
|
||||
@@ -5,8 +5,17 @@
|
||||
|
||||
package com.wireguard.android.util
|
||||
|
||||
import android.icu.text.ListFormatter
|
||||
import android.icu.text.MeasureFormat
|
||||
import android.icu.text.RelativeDateTimeFormatter
|
||||
import android.icu.util.Measure
|
||||
import android.icu.util.MeasureUnit
|
||||
import android.os.Build
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import java.util.Locale
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.toDuration
|
||||
|
||||
object QuantityFormatter {
|
||||
fun formatBytes(bytes: Long): String {
|
||||
@@ -19,4 +28,40 @@ object QuantityFormatter {
|
||||
else -> context.getString(R.string.transfer_tibibytes, bytes / (1024.0 * 1024.0 * 1024.0) / 1024.0)
|
||||
}
|
||||
}
|
||||
|
||||
fun formatEpochAgo(epochMillis: Long): String {
|
||||
var span = (System.currentTimeMillis() - epochMillis) / 1000
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
|
||||
return Application.get().applicationContext.getString(R.string.latest_handshake_ago, span.toDuration(DurationUnit.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)
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,12 @@ object UserKnobs {
|
||||
it[DARK_THEME] ?: false
|
||||
}
|
||||
|
||||
suspend fun setDarkTheme(on: Boolean) {
|
||||
Application.getPreferencesDataStore().edit {
|
||||
it[DARK_THEME] = on
|
||||
}
|
||||
}
|
||||
|
||||
private val ALLOW_REMOTE_CONTROL_INTENTS = booleanPreferencesKey("allow_remote_control_intents")
|
||||
val allowRemoteControlIntents: Flow<Boolean>
|
||||
get() = Application.getPreferencesDataStore().data.map {
|
||||
|
||||
@@ -4,22 +4,27 @@
|
||||
*/
|
||||
package com.wireguard.android.viewmodel
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import androidx.core.os.ParcelCompat
|
||||
import androidx.databinding.ObservableArrayList
|
||||
import androidx.databinding.ObservableList
|
||||
import com.wireguard.config.BadConfigException
|
||||
import com.wireguard.config.Config
|
||||
import com.wireguard.config.Peer
|
||||
import java.util.ArrayList
|
||||
|
||||
class ConfigProxy : Parcelable {
|
||||
val `interface`: InterfaceProxy
|
||||
val peers: ObservableList<PeerProxy> = ObservableArrayList()
|
||||
|
||||
private constructor(parcel: Parcel) {
|
||||
`interface` = InterfaceProxy.CREATOR.createFromParcel(parcel)
|
||||
parcel.readTypedList(peers, PeerProxy.CREATOR)
|
||||
`interface` = ParcelCompat.readParcelable(parcel, InterfaceProxy::class.java.classLoader, InterfaceProxy::class.java) ?: InterfaceProxy()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
ParcelCompat.readParcelableList(parcel, peers, PeerProxy::class.java.classLoader, PeerProxy::class.java)
|
||||
} else {
|
||||
parcel.readTypedList(peers, PeerProxy.CREATOR)
|
||||
}
|
||||
peers.forEach { it.bind(this) }
|
||||
}
|
||||
|
||||
@@ -57,7 +62,11 @@ class ConfigProxy : Parcelable {
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeParcelable(`interface`, flags)
|
||||
dest.writeTypedList(peers)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
dest.writeParcelableList(peers, flags)
|
||||
} else {
|
||||
dest.writeTypedList(peers)
|
||||
}
|
||||
}
|
||||
|
||||
private class ConfigProxyCreator : Parcelable.Creator<ConfigProxy> {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.wireguard.android.R
|
||||
|
||||
class TvCardView(context: Context?, attrs: AttributeSet?) : MaterialCardView(context, attrs) {
|
||||
var isUp: Boolean = false
|
||||
set(value) {
|
||||
field = value
|
||||
refreshDrawableState()
|
||||
}
|
||||
var isDeleting: Boolean = false
|
||||
set(value) {
|
||||
field = value
|
||||
refreshDrawableState()
|
||||
}
|
||||
|
||||
override fun onCreateDrawableState(extraSpace: Int): IntArray {
|
||||
if (isUp || isDeleting) {
|
||||
val drawableState = super.onCreateDrawableState(extraSpace + (if (isUp) 1 else 0) + (if (isDeleting) 1 else 0))
|
||||
if (isUp) {
|
||||
View.mergeDrawableStates(drawableState, STATE_IS_UP)
|
||||
}
|
||||
if (isDeleting) {
|
||||
View.mergeDrawableStates(drawableState, STATE_IS_DELETING)
|
||||
}
|
||||
return drawableState
|
||||
}
|
||||
return super.onCreateDrawableState(extraSpace)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val STATE_IS_UP = intArrayOf(R.attr.state_isUp)
|
||||
private val STATE_IS_DELETING = intArrayOf(R.attr.state_isDeleting)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item app:state_isUp="true" app:state_isDeleting="false" android:color="?attr/colorPrimaryInverse" />
|
||||
<item android:state_focused="true" app:state_isDeleting="true" android:color="?attr/colorErrorContainer" />
|
||||
<item android:color="?attr/colorOnSurfaceInverse" />
|
||||
</selector>
|
||||
@@ -6,7 +6,7 @@
|
||||
<item
|
||||
android:state_activated="true"
|
||||
app:state_multiselected="true">
|
||||
<color android:color="?attr/colorMultiselectActiveBackground" />
|
||||
<color android:color="?attr/colorSurfaceVariant" />
|
||||
</item>
|
||||
<item
|
||||
android:state_activated="true">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/import_button"
|
||||
style="@style/Widget.Material3.Button.IconButton"
|
||||
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
@@ -135,13 +135,12 @@
|
||||
android:visibility="@{isDeleting ? View.GONE : View.VISIBLE}"
|
||||
app:icon="@{filesRoot.isEmpty ? @drawable/ic_action_add_white : @drawable/ic_arrow_back}"
|
||||
app:iconPadding="0dp"
|
||||
app:iconTint="?attr/colorOnPrimary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/delete_button"
|
||||
style="@style/Widget.Material3.Button.IconButton"
|
||||
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
@@ -149,7 +148,6 @@
|
||||
android:visibility="@{((tunnels.isEmpty && !isDeleting) || !filesRoot.isEmpty) ? View.GONE : View.VISIBLE}"
|
||||
app:icon="@{isDeleting ? @drawable/ic_arrow_back : @drawable/ic_action_delete}"
|
||||
app:iconPadding="0dp"
|
||||
app:iconTint="?attr/colorOnPrimary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
android:layout_margin="8dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="0dp"
|
||||
android:backgroundTint="@color/tv_card_background"
|
||||
android:checkable="true"
|
||||
android:focusable="true"
|
||||
app:contentPadding="8dp">
|
||||
@@ -35,7 +34,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@{key}"
|
||||
android:textAppearance="?attr/textAppearanceTitleLarge"
|
||||
android:textColor="?attr/colorOnPrimary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
@@ -28,16 +28,18 @@
|
||||
type="com.wireguard.android.model.ObservableTunnel" />
|
||||
</data>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
<com.wireguard.android.widget.TvCardView
|
||||
android:layout_width="225dp"
|
||||
android:layout_height="110dp"
|
||||
android:layout_margin="8dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="0dp"
|
||||
android:backgroundTint="@color/tv_list_item_tint"
|
||||
android:checkable="true"
|
||||
android:focusable="true"
|
||||
app:contentPadding="8dp"
|
||||
android:backgroundTint="@{(item.state == State.UP && !isDeleting) ? @color/tv_secondary_dark_color : (isDeleting && isFocused) ? @color/tv_card_delete_background : @color/tv_card_background}">
|
||||
app:isDeleting="@{isDeleting}"
|
||||
app:isUp="@{item.state == State.UP}">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -49,7 +51,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@{item.name}"
|
||||
android:textAppearance="?attr/textAppearanceTitleLarge"
|
||||
android:textColor="?attr/colorOnPrimary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@sample/interface_names.json/names/names/name" />
|
||||
@@ -76,6 +77,6 @@
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
</com.wireguard.android.widget.TvCardView>
|
||||
|
||||
</layout>
|
||||
|
||||
@@ -111,6 +111,8 @@
|
||||
<string name="tv_select_a_storage_drive">تحديد محرك تخزين</string>
|
||||
<string name="tv_no_file_picker">الرجاء تثبيت مدير ملفات لتتمكن من تصفح الملفات</string>
|
||||
<string name="tv_add_tunnel_get_started">أضف نفقاً لتبدأ</string>
|
||||
<string name="donate_title">♥️ تبرع لمشروع وايرجارد</string>
|
||||
<string name="donate_summary">كل مساهمة تساعد</string>
|
||||
<string name="disable_config_export_title">تعطيل تصدير التكوين</string>
|
||||
<string name="disable_config_export_description">تعطيل تصدير الإعدادات يجعل المفاتيح الخاصة غير متاحة</string>
|
||||
<string name="dns_servers">خوادم DNS</string>
|
||||
@@ -120,6 +122,7 @@
|
||||
<string name="error_down">خطأ في فصل النفق:%s</string>
|
||||
<string name="error_fetching_apps">خطأ في جلب قائمة التطبيقات: %s</string>
|
||||
<string name="error_root">الرجاء الحصول على صلاحيات الروت وحاول مرة أخرى</string>
|
||||
<string name="error_prepare">خطأ في تحضير النفق: %s</string>
|
||||
<string name="error_up">خطأ خلال إنشاء النفق: %s</string>
|
||||
<string name="exclude_private_ips">استبعاد عناوين بروتوكول الإنترنت (IP) الخاصة</string>
|
||||
<string name="generate_new_private_key">توليد مفتاح خاص جديد</string>
|
||||
@@ -215,6 +218,7 @@
|
||||
<string name="tunnel_create_success">تم إنشاء نفق \"%s\" بنجاح</string>
|
||||
<string name="tunnel_error_already_exists">النفق \"%s\" موجود بالفعل</string>
|
||||
<string name="tunnel_error_invalid_name">إسم غير صالح</string>
|
||||
<string name="tunnel_list_placeholder">أضف نفق باستخدام الزر أدناه</string>
|
||||
<string name="tunnel_name">إسم النفق</string>
|
||||
<string name="tunnel_on_error">غير قادر على تشغيل النفق (wgTurnOn أعاد %d)</string>
|
||||
<string name="tunnel_dns_failure">غير قادر على حل اسم مضيف نظام أسماء النطاقات (DNS hostname): \"%s\"</string>
|
||||
|
||||
@@ -72,6 +72,8 @@
|
||||
<string name="config_file_exists_error">Konfigurationsfilen \"%s\" findes allerede</string>
|
||||
<string name="config_not_found_error">Konfigurationsfilen \"%s\" blev ikke fundet</string>
|
||||
<string name="config_rename_error">Kan ikke omdøbe konfigurationsfilen \"%s\"</string>
|
||||
<string name="config_save_error">Kan ikke gemme konfigurationen for \"%1$s\": %2$s</string>
|
||||
<string name="config_save_success">Konfiguration for \"%s\" blev gemt</string>
|
||||
<string name="create_activity_title">Opret WireGuard tunnel</string>
|
||||
<string name="create_empty">Opret fra ny</string>
|
||||
<string name="create_from_file">Importér fra fil eller arkiv</string>
|
||||
@@ -88,6 +90,7 @@
|
||||
<string name="dns_search_domains">DNS-søgedomæner</string>
|
||||
<string name="edit">Redigér</string>
|
||||
<string name="endpoint">Slutpunkt</string>
|
||||
<string name="error_prepare">Fejl ved forberedelse af tunnel: %s</string>
|
||||
<string name="error_up">Fejl under aktivering af tunnel: %s</string>
|
||||
<string name="exclude_private_ips">Eksludér private IP-adresser</string>
|
||||
<string name="generate_new_private_key">Generér ny privat nøgle</string>
|
||||
@@ -152,6 +155,7 @@
|
||||
<string name="tunnel_create_success">Tunnelen blev succesfuldt oprettet \"%s\"</string>
|
||||
<string name="tunnel_error_already_exists">Tunnel \"%s\" eksisterer allerede</string>
|
||||
<string name="tunnel_error_invalid_name">Ugyldigt navn</string>
|
||||
<string name="tunnel_list_placeholder">Tilføj en tunnel ved hjælp af knappen nedenfor</string>
|
||||
<string name="tunnel_name">Tunnel Navn</string>
|
||||
<string name="tunnel_dns_failure">Kunne ikke opslå DNS adresse: \"%s\"</string>
|
||||
<string name="tunnel_rename_error">Kan ikke omdøbe tunnel: %s</string>
|
||||
|
||||
@@ -107,6 +107,8 @@
|
||||
<string name="tv_select_a_storage_drive">Sélectionner un disque de stockage</string>
|
||||
<string name="tv_no_file_picker">Veuillez installer un utilitaire de gestion de fichiers pour parcourir les fichiers</string>
|
||||
<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="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>
|
||||
@@ -213,6 +215,7 @@
|
||||
<string name="tunnel_create_success">Tunnel «%s » créé avec succès</string>
|
||||
<string name="tunnel_error_already_exists">Le tunnel « %s » existe déjà</string>
|
||||
<string name="tunnel_error_invalid_name">Nom invalide</string>
|
||||
<string name="tunnel_list_placeholder">Ajoutez un tunnel en utilisant le bouton ci-dessous</string>
|
||||
<string name="tunnel_name">Nom du tunnel</string>
|
||||
<string name="tunnel_on_error">Impossible d\'activer le tunnel (wgTurnOn a retourné %d)</string>
|
||||
<string name="tunnel_dns_failure">Impossible de résoudre le nom d\'hôte DNS: “%s”</string>
|
||||
|
||||
@@ -27,6 +27,5 @@
|
||||
<item name="colorOnSurfaceInverse">@color/md_theme_dark_inverseOnSurface</item>
|
||||
<item name="colorSurfaceInverse">@color/md_theme_dark_inverseSurface</item>
|
||||
<item name="colorPrimaryInverse">@color/md_theme_dark_inversePrimary</item>
|
||||
<item name="colorMultiselectActiveBackground">@color/md_theme_dark_surfaceVariant</item>
|
||||
</style>
|
||||
</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>
|
||||
|
||||
@@ -133,6 +133,9 @@
|
||||
<string name="tv_select_a_storage_drive">Выберите накопитель</string>
|
||||
<string name="tv_no_file_picker">Пожалуйста, установите утилиту управления файлами для их просмотра</string>
|
||||
<string name="tv_add_tunnel_get_started">Добавьте туннель, чтобы начать</string>
|
||||
<string name="donate_title">♥ Пожертвовать проекту WireGuard</string>
|
||||
<string name="donate_summary">Каждое пожертвование помогает</string>
|
||||
<string name="donate_google_play_disappointment">Спасибо за поддержку проекта WireGuard!\n\nК сожалению, из-за политики Google, нельзя размещать ссылку на тот раздел сайта проекта, где можно сделать пожертвование. Надеемся, вы сможете разобраться самостоятельно!\n\nЕщё раз спасибо за ваш вклад.</string>
|
||||
<string name="disable_config_export_title">Отключить экспорт конфигурации</string>
|
||||
<string name="disable_config_export_description">Отключение экспорта конфигурации делает приватные ключи менее доступными</string>
|
||||
<string name="dns_servers">DNS-серверы</string>
|
||||
@@ -142,6 +145,7 @@
|
||||
<string name="error_down">Ошибка при выходе из туннеля: %s</string>
|
||||
<string name="error_fetching_apps">Ошибка при получении списка приложений: %s</string>
|
||||
<string name="error_root">Пожалуйста, получите root-доступ и попробуйте снова</string>
|
||||
<string name="error_prepare">Ошибка при подготовке туннеля: %s</string>
|
||||
<string name="error_up">Ошибка при запуске туннеля: %s</string>
|
||||
<string name="exclude_private_ips">Исключить частные IP-адреса</string>
|
||||
<string name="generate_new_private_key">Сгенерировать новый приватный ключ</string>
|
||||
@@ -161,6 +165,8 @@
|
||||
<string name="key_length_explanation_base64">: ключи WireGuard base64 должны содержать 44 символа (32 байта)</string>
|
||||
<string name="key_length_explanation_binary">: ключи WireGuard должны быть 32 байта</string>
|
||||
<string name="key_length_explanation_hex">: HEX-ключи WireGuard должны содержать 64 символа (32 байта)</string>
|
||||
<string name="latest_handshake">Последнее рукопожатие</string>
|
||||
<string name="latest_handshake_ago">%s назад</string>
|
||||
<string name="listen_port">Порт</string>
|
||||
<string name="log_export_error">Не удалось экспортировать журнал: %s</string>
|
||||
<string name="log_export_subject">Файл журнала WireGuard Android</string>
|
||||
@@ -239,6 +245,7 @@
|
||||
<string name="tunnel_create_success">Успешно создан туннель “%s”</string>
|
||||
<string name="tunnel_error_already_exists">Туннель “%s” уже существует</string>
|
||||
<string name="tunnel_error_invalid_name">Неправильное имя</string>
|
||||
<string name="tunnel_list_placeholder">Добавьте туннель с помощью кнопки ниже</string>
|
||||
<string name="tunnel_name">Название туннеля</string>
|
||||
<string name="tunnel_on_error">Не удалось включить туннель (wgTurnOn вернул %d)</string>
|
||||
<string name="tunnel_dns_failure">Не удалось определить DNS имя: “%s”</string>
|
||||
|
||||
@@ -107,6 +107,9 @@
|
||||
<string name="tv_select_a_storage_drive">Bir depolama sürücüsü seçin</string>
|
||||
<string name="tv_no_file_picker">Dosyalara göz atmak için lütfen bir dosya yönetim aracı yükleyin</string>
|
||||
<string name="tv_add_tunnel_get_started">Başlamak için bir tünel ekleyin</string>
|
||||
<string name="donate_title">♥ WireGuard Projesine Bağış Yapın</string>
|
||||
<string name="donate_summary">Her katkı yardımcı olur</string>
|
||||
<string name="donate_google_play_disappointment">WireGuard Projesini desteklediğiniz için teşekkür ederiz!\n\nNe yazık ki, Google\'ın politikaları nedeniyle, proje web sayfasının bağış yapabileceğiniz bölümüne bağlantı vermemize izin verilmiyor. Umarım bunu çözebilirsin!\n\nKatkılarınız için tekrar teşekkürler.</string>
|
||||
<string name="disable_config_export_title">Ayarları dışa aktarmayı kapa</string>
|
||||
<string name="disable_config_export_description">Ayarları dışa aktarmayı kapamak gizli anahtarları daha az erişilebilir kılar</string>
|
||||
<string name="dns_servers">DNS sunucuları</string>
|
||||
@@ -213,6 +216,7 @@
|
||||
<string name="tunnel_create_success">Tünel başarıyla oluşturuldu “%s”</string>
|
||||
<string name="tunnel_error_already_exists">“%s” tüneli zaten mevcut</string>
|
||||
<string name="tunnel_error_invalid_name">Geçersiz isim</string>
|
||||
<string name="tunnel_list_placeholder">Aşağıdaki düğmeyi kullanarak bir tünel ekleyin</string>
|
||||
<string name="tunnel_name">Tünel İsmi</string>
|
||||
<string name="tunnel_on_error">Tünel aktif edilemiyor (wgTornOn döndürdü: %d)</string>
|
||||
<string name="tunnel_dns_failure">DNS adı çözülemedi: \"%s\"</string>
|
||||
|
||||
@@ -94,6 +94,9 @@
|
||||
<string name="tv_select_a_storage_drive">选择一个存储驱动器</string>
|
||||
<string name="tv_no_file_picker">请安装一个文件管理工具以浏览文件</string>
|
||||
<string name="tv_add_tunnel_get_started">添加第一条网络隧道</string>
|
||||
<string name="donate_title">♥ 为 WireGuard 捐赠</string>
|
||||
<string name="donate_summary">无论多寡,聚沙成塔</string>
|
||||
<string name="donate_google_play_disappointment">感谢您对 WireGuard 项目的支持!\n\n只可惜,受谷歌的政策所限,我们不能在此展示项目捐赠页面的链接,还望您自行访问捐赠页面!\n\n再次感谢您的贡献。</string>
|
||||
<string name="disable_config_export_title">禁止导出配置</string>
|
||||
<string name="disable_config_export_description">禁止导出配置可降低私钥泄露的风险</string>
|
||||
<string name="dns_servers">DNS 服务器</string>
|
||||
@@ -103,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>
|
||||
@@ -122,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>
|
||||
@@ -132,7 +138,7 @@
|
||||
<string name="log_viewer_pref_title">查看应用日志</string>
|
||||
<string name="log_viewer_title">日志</string>
|
||||
<string name="logcat_error">无法运行 logcat: </string>
|
||||
<string name="module_enabler_disabled_summary">WireGuard-Andorid 的内核模块尚处于实验阶段。它能够增强性能,但存在潜在的不稳定因素,请谨慎开启</string>
|
||||
<string name="module_enabler_disabled_summary">内核模块(实验性)能够增强性能,启用时需谨慎</string>
|
||||
<string name="module_enabler_disabled_title">启用内核模块</string>
|
||||
<string name="module_enabler_enabled_summary">用户空间的模块性能较弱,但稳定性更好</string>
|
||||
<string name="module_enabler_enabled_title">停用内核模块</string>
|
||||
@@ -200,6 +206,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>
|
||||
@@ -208,7 +215,7 @@
|
||||
<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="version_summary">%1$s 后端 %2$s</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>
|
||||
<string name="version_title">WireGuard for Android v%s</string>
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
<resources>
|
||||
<declare-styleable name="Multiselected">
|
||||
<attr name="state_multiselected" format="boolean" />
|
||||
<attr name="colorMultiselectActiveBackground" format="reference|color" />
|
||||
</declare-styleable>
|
||||
<declare-styleable name="TvCardView">
|
||||
<attr name="state_isUp" format="boolean" />
|
||||
<attr name="state_isDeleting" format="boolean" />
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
<string name="error_down">Error bringing down tunnel: %s</string>
|
||||
<string name="error_fetching_apps">Error fetching apps list: %s</string>
|
||||
<string name="error_root">Please obtain root access and try again</string>
|
||||
<string name="error_prepare">Error preparing tunnel: %s</string>
|
||||
<string name="error_up">Error bringing up tunnel: %s</string>
|
||||
<string name="exclude_private_ips">Exclude private IPs</string>
|
||||
<string name="generate_new_private_key">Generate new private key</string>
|
||||
@@ -139,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>
|
||||
|
||||
@@ -9,10 +9,11 @@
|
||||
<item name="materialCardViewStyle">@style/WireGuardTheme.MaterialCardView</item>
|
||||
<item name="toolbarStyle">@style/WireGuardTheme.Toolbar</item>
|
||||
<item name="bottomSheetDialogTheme">@style/WireGuardTheme.BottomSheetDialog</item>
|
||||
<item name="android:statusBarColor">@null</item>
|
||||
</style>
|
||||
|
||||
<!-- Various additional API-specific features in values-v*/styles.xml -->
|
||||
<style name="AppTheme" parent="AppThemeBase"/>
|
||||
<style name="AppTheme" parent="AppThemeBase" />
|
||||
|
||||
<style name="WireGuardTheme.MaterialCardView" parent="Widget.Material3.CardView.Elevated">
|
||||
<item name="cornerRadius">4dp</item>
|
||||
@@ -36,4 +37,9 @@
|
||||
<item name="android:windowEnterAnimation">@android:anim/fade_in</item>
|
||||
<item name="android:windowExitAnimation">@android:anim/fade_out</item>
|
||||
</style>
|
||||
|
||||
<style name="TvTheme" parent="AppTheme">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -27,6 +27,5 @@
|
||||
<item name="colorOnSurfaceInverse">@color/md_theme_light_inverseOnSurface</item>
|
||||
<item name="colorSurfaceInverse">@color/md_theme_light_inverseSurface</item>
|
||||
<item name="colorPrimaryInverse">@color/md_theme_light_inversePrimary</item>
|
||||
<item name="colorMultiselectActiveBackground">@color/md_theme_light_surfaceVariant</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="tv_primary_color">#ff212121</color>
|
||||
<color name="tv_secondary_dark_color">#ff0059c1</color>
|
||||
<color name="tv_card_background">@color/tv_primary_color</color>
|
||||
<color name="tv_card_delete_background">#b00020</color>
|
||||
<color name="tv_list_multiselect_background">#ffeeeeee</color>
|
||||
</resources>
|
||||
@@ -1,31 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<style name="TvTheme" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<item name="colorPrimary">@color/tv_primary_color</item>
|
||||
<item name="colorOnPrimary">#fffafafa</item>
|
||||
<item name="colorPrimaryDark">@color/tv_primary_color</item>
|
||||
<item name="colorPrimaryVariant">#ff484848</item>
|
||||
<item name="colorSecondary">#ff4285f4</item>
|
||||
<item name="colorOnSecondary">#ff0059c1</item>
|
||||
<item name="colorSurface">@color/tv_primary_color</item>
|
||||
<item name="colorOnSurface">#fffafafa</item>
|
||||
<item name="colorMultiselectActiveBackground">@color/tv_list_multiselect_background</item>
|
||||
<item name="colorControlNormal">#fffafafa</item>
|
||||
<item name="elevationOverlayEnabled">false</item>
|
||||
<item name="android:statusBarColor">@color/tv_primary_color</item>
|
||||
<item name="android:windowBackground">@color/tv_primary_color</item>
|
||||
<item name="alertDialogTheme">@style/TvTheme.Dialog</item>
|
||||
<item name="materialAlertDialogTheme">@style/TvTheme.Dialog</item>
|
||||
<item name="materialCardViewStyle">@style/TvTheme.MaterialCardView</item>
|
||||
</style>
|
||||
|
||||
<style name="TvTheme.Dialog" parent="Theme.Material3.DayNight.Dialog.Alert">
|
||||
<item name="android:windowBackground">?attr/colorSurface</item>
|
||||
</style>
|
||||
|
||||
<style name="TvTheme.MaterialCardView" parent="Widget.Material3.CardView.Elevated">
|
||||
<item name="cornerRadius">4dp</item>
|
||||
<item name="contentPadding">8dp</item>
|
||||
</style>
|
||||
</resources>
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
buildscript {
|
||||
ext {
|
||||
wireguardVersionCode = 497
|
||||
wireguardVersionName = '1.0.20230330'
|
||||
wireguardVersionCode = 500
|
||||
wireguardVersionName = '1.0.20230427'
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user