mirror of
https://github.com/amnezia-vpn/amneziawg-android.git
synced 2026-07-03 14:05:13 +02:00
Compare commits
188 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c762bc3120 | |||
| aeb8903ba2 | |||
| 2f244771a2 | |||
| 5ffb1d0c36 | |||
| 62910caae1 | |||
| 866fd35df5 | |||
| 32a2ebb66f | |||
| 70d9a87d0d | |||
| 0bd39309c8 | |||
| 751ce54fa5 | |||
| 72dc677819 | |||
| 7002367e4f | |||
| 5339e48251 | |||
| d626e23210 | |||
| 342ed5e259 | |||
| 2bf09f8e2d | |||
| 36d93a8e1b | |||
| 1d7d306950 | |||
| 06306c669d | |||
| d19e126e2e | |||
| bf84e26a86 | |||
| a03ad51622 | |||
| 447b06ec2c | |||
| 6da1d5ed01 | |||
| 836c367499 | |||
| 15cb99a41f | |||
| fae74dd3ca | |||
| e462ae270d | |||
| 24d5a28a4a | |||
| 3df42898ae | |||
| 1701e4fde4 | |||
| f6d175b774 | |||
| 878c450563 | |||
| b444ea064e | |||
| 4eb90266a2 | |||
| 3935a369b8 | |||
| 32fc760053 | |||
| 5459c6d531 | |||
| a1889b801c | |||
| a86dff9433 | |||
| 02f59b6b27 | |||
| 29bb93cdf7 | |||
| eae06aade7 | |||
| a8b2fc15de | |||
| f699d9cf9a | |||
| ae00e8ce6e | |||
| 2730e75474 | |||
| 00e0224fd1 | |||
| 82c9b75ea1 | |||
| a23c57bab0 | |||
| 02b92c178a | |||
| 8dd6c3e35e | |||
| 78f2fda6ca | |||
| 2f822aa1f6 | |||
| 40f2f073cd | |||
| 37fabf0229 | |||
| fe61522f2a | |||
| 24ded8070f | |||
| 24569a4c49 | |||
| b448d605f0 | |||
| 0dcfde35f4 | |||
| 6acc1125b8 | |||
| 94ecb13d2f | |||
| 6008efcd95 | |||
| d6a8e9d4dc | |||
| bcd8c33005 | |||
| e71c84a785 | |||
| 3474849b1d | |||
| f32c5cc222 | |||
| cf6e1490f4 | |||
| 86951f509c | |||
| 8e9b2c6f05 | |||
| 9ace8dd928 | |||
| 8f727903f3 | |||
| b6aa09bc19 | |||
| 782550318a | |||
| d02543639b | |||
| e1e16cdf3b | |||
| 1bbcf83f43 | |||
| 169bd408f2 | |||
| 777e3b71ba | |||
| 511e54764e | |||
| d0b5fb5107 | |||
| 7c3137e2c8 | |||
| 12a3ec28bc | |||
| 892295c42a | |||
| ea9ef4af24 | |||
| b8870f3599 | |||
| 11301617d2 | |||
| 617be576f8 | |||
| 8523979c9c | |||
| 8768acee68 | |||
| a73d2121e1 | |||
| dd58fad69e | |||
| ba9d689ef9 | |||
| 97d82740d0 | |||
| 0750c29c7f | |||
| 639ee6b5b2 | |||
| f764f2aaa9 | |||
| d2a9c56d9c | |||
| 4c3289aa52 | |||
| 79376bca64 | |||
| b709d36c01 | |||
| 0301c85e2c | |||
| 7e979817e2 | |||
| 13cc4436ca | |||
| bcfbf37ed1 | |||
| 74ff870cf5 | |||
| b4f54e8958 | |||
| d179a3af15 | |||
| 2bac2feeb5 | |||
| 261236c8ce | |||
| 4a7969ef24 | |||
| f670ff22c6 | |||
| cb3194f10a | |||
| f6b2bbf433 | |||
| 56a4862442 | |||
| 20390d65c8 | |||
| 177457e67b | |||
| 8caec4d739 | |||
| fb819b99a4 | |||
| fe82037f06 | |||
| c2aa1b21f8 | |||
| d69415b55a | |||
| 4fae2d1255 | |||
| a300f269f1 | |||
| 961cba3f7c | |||
| 6bc7386bff | |||
| 5fa08f286e | |||
| 35f868733c | |||
| e71b3d2583 | |||
| 755148242c | |||
| 15fea6f02f | |||
| cb2842e8ef | |||
| 106b67d892 | |||
| 996587f792 | |||
| 3a4bf35c77 | |||
| 46b37c0c26 | |||
| 5b5ba88a97 | |||
| ceb3095a0a | |||
| a31f0cf788 | |||
| 1dc74b171c | |||
| 9266487fe5 | |||
| 5d7ce139bc | |||
| ddb6c87ebf | |||
| 8a6f8f73cd | |||
| f4fc15538d | |||
| 938399d881 | |||
| 53ca421a85 | |||
| 32778d1c03 | |||
| a870bf6e04 | |||
| 7a8f708157 | |||
| e729c5dc51 | |||
| 4bf34c49b7 | |||
| 05511d4900 | |||
| 15da17b595 | |||
| b3c43e428f | |||
| 7bec539722 | |||
| a8dfebb086 | |||
| e72b4fc144 | |||
| 03189e7b20 | |||
| 10bb413187 | |||
| 1c814310b9 | |||
| 3fe9e3162f | |||
| 6da6f7886a | |||
| 8c2029870f | |||
| a5031a44a0 | |||
| 44b27fe472 | |||
| 93fb3b345b | |||
| 8b596697b7 | |||
| c536bbb7e9 | |||
| a978aac129 | |||
| eb8cab4110 | |||
| 0a36d9a5e9 | |||
| 309571039d | |||
| d56f2fb1bb | |||
| 9df8e5e239 | |||
| 444a86cc9f | |||
| 382e10e103 | |||
| dc002d77fa | |||
| aaa55c0dcc | |||
| 0ad3781ae5 | |||
| d738161a2e | |||
| 52c2e9cd24 | |||
| 5fd1a32ae4 | |||
| 655a853857 | |||
| 847da23300 | |||
| d5c07374ff |
Generated
+4
-9
@@ -62,17 +62,9 @@
|
||||
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="PACKAGES_IMPORT_LAYOUT">
|
||||
<value>
|
||||
<package name="" alias="false" withSubpackages="true" />
|
||||
<package name="java" alias="false" withSubpackages="true" />
|
||||
<package name="javax" alias="false" withSubpackages="true" />
|
||||
<package name="kotlin" alias="false" withSubpackages="true" />
|
||||
<package name="" alias="true" withSubpackages="true" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="10" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="10" />
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<XML>
|
||||
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
|
||||
@@ -477,5 +469,8 @@
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
<component name="CopyrightManager">
|
||||
<copyright>
|
||||
<option name="notice" value="Copyright © &#36;today.year WireGuard LLC. All Rights Reserved. SPDX-License-Identifier: Apache-2.0" />
|
||||
<option name="notice" value="Copyright © 2017-&#36;today.year WireGuard LLC. All Rights Reserved. SPDX-License-Identifier: Apache-2.0" />
|
||||
<option name="myName" value="Default" />
|
||||
</copyright>
|
||||
</component>
|
||||
|
||||
@@ -16,18 +16,22 @@ macOS users may need [flock(1)](https://github.com/discoteq/flock).
|
||||
|
||||
## Embedding
|
||||
|
||||
The tunnel library is [on JCenter](https://bintray.com/wireguard/wireguard-android/wireguard-android/_latestVersion), alongside [extensive class library documentation](https://javadoc.io/doc/com.wireguard.android/tunnel).
|
||||
The tunnel library is [on Maven Central](https://search.maven.org/artifact/com.wireguard.android/tunnel), alongside [extensive class library documentation](https://javadoc.io/doc/com.wireguard.android/tunnel).
|
||||
|
||||
```
|
||||
implementation 'com.wireguard.android:tunnel:$wireguardTunnelVersion'
|
||||
```
|
||||
|
||||
The library makes use of Java 8 features, so be sure to support those in your gradle configuration:
|
||||
The library makes use of Java 8 features, so be sure to support those in your gradle configuration with [desugaring](https://developer.android.com/studio/write/java8-support#library-desugaring):
|
||||
|
||||
```
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
coreLibraryDesugaringEnabled = true
|
||||
}
|
||||
dependencies {
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
+49
-39
@@ -1,47 +1,58 @@
|
||||
buildscript {
|
||||
ext {
|
||||
activityVersion = '1.2.0-alpha07'
|
||||
agpVersion = '4.0.1'
|
||||
annotationsVersion = '1.1.0'
|
||||
appcompatVersion = '1.2.0'
|
||||
bintrayPluginVersion = '1.8.5'
|
||||
biometricVersion = '1.1.0-alpha02'
|
||||
collectionVersion = '1.1.0'
|
||||
constraintLayoutVersion = '2.0.1'
|
||||
coordinatorLayoutVersion = '1.1.0'
|
||||
coreKtxVersion = '1.3.1'
|
||||
coroutinesVersion = '1.3.9'
|
||||
datastoreVersion = '1.0.0-alpha01'
|
||||
desugarVersion = '1.0.10'
|
||||
fragmentVersion = '1.3.0-alpha07'
|
||||
activityVersion = '1.4.0'
|
||||
annotationsVersion = '1.3.0'
|
||||
appcompatVersion = '1.4.1'
|
||||
biometricVersion = '1.1.0'
|
||||
collectionVersion = '1.2.0'
|
||||
constraintLayoutVersion = '2.1.3'
|
||||
coordinatorLayoutVersion = '1.2.0'
|
||||
coreKtxVersion = '1.7.0'
|
||||
coroutinesVersion = '1.6.1'
|
||||
datastoreVersion = '1.0.0'
|
||||
desugarVersion = '1.1.5'
|
||||
fragmentVersion = '1.4.1'
|
||||
jsr305Version = '3.0.2'
|
||||
junitVersion = '4.13'
|
||||
kotlinVersion = '1.4.10'
|
||||
lifecycleRuntimeKtxVersion = '2.3.0-alpha07'
|
||||
materialComponentsVersion = '22973ff10305397bfc72fed41684175fee8aeefb'
|
||||
mavenPluginVersion = '2.1'
|
||||
preferenceVersion = '1.1.1'
|
||||
zxingEmbeddedVersion = '3.6.0'
|
||||
junitVersion = '4.13.2'
|
||||
lifecycleRuntimeKtxVersion = '2.4.1'
|
||||
materialComponentsVersion = '1.5.0'
|
||||
preferenceVersion = '1.2.0'
|
||||
zxingEmbeddedVersion = '4.3.0'
|
||||
|
||||
groupName = 'com.wireguard.android'
|
||||
}
|
||||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:$agpVersion"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||
classpath "com.github.dcendents:android-maven-gradle-plugin:$mavenPluginVersion"
|
||||
classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:$bintrayPluginVersion"
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
maven { url 'https://jitpack.io' }
|
||||
plugins {
|
||||
id "de.undercouch.download" version "5.0.4"
|
||||
}
|
||||
|
||||
task downloadCrowdin(type: Download) {
|
||||
src 'https://crowdin.com/backend/download/project/wireguard.zip'
|
||||
dest file('build/translations.zip')
|
||||
overwrite true
|
||||
}
|
||||
|
||||
task cleanCrowdin(type: Delete) {
|
||||
delete 'ui/src/main/res/values-*/strings.xml'
|
||||
}
|
||||
|
||||
task extractCrowdin(type: Copy, dependsOn: ['downloadCrowdin', 'cleanCrowdin']) {
|
||||
mustRunAfter 'downloadCrowdin'
|
||||
from zipTree(file('build/translations.zip'))
|
||||
into file('build/translations')
|
||||
doFirst {
|
||||
delete 'build/translations'
|
||||
}
|
||||
}
|
||||
|
||||
task crowdin(type: Copy, dependsOn: ['extractCrowdin']) {
|
||||
mustRunAfter 'extractCrowdin'
|
||||
from 'build/translations/wireguard-android/ui/src/main/res'
|
||||
into 'ui/src/main/res/'
|
||||
doLast {
|
||||
delete 'build/translations'
|
||||
delete 'build/translations.zip'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,9 +62,8 @@ task clean(type: Delete) {
|
||||
|
||||
tasks {
|
||||
wrapper {
|
||||
gradleVersion = "6.6.1"
|
||||
distributionType = Wrapper.DistributionType.ALL
|
||||
distributionSha256Sum = "11657af6356b7587bfb37287b5992e94a9686d5c8a0a1b60b87b9928a2decde5"
|
||||
gradleVersion = "7.3.3"
|
||||
distributionSha256Sum = "b586e04868a22fd817c8971330fec37e298f3242eb85c374181b12d637f80302"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+62
-23
@@ -1,33 +1,72 @@
|
||||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.configureondemand=true
|
||||
org.gradle.caching=true
|
||||
|
||||
# R8 Full mode
|
||||
android.enableR8.fullMode=true
|
||||
# Enable Kotlin incremental compilation
|
||||
kotlin.incremental=true
|
||||
|
||||
# https://jakewharton.com/increased-accuracy-of-aapt2-keep-rules/
|
||||
android.useMinimalKeepRules=true
|
||||
# Enable AndroidX support
|
||||
android.useAndroidX=true
|
||||
|
||||
# Enable rudimentary R class namespacing where each library only contains
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
|
||||
# Turn off AP discovery in compile path to enable compile avoidance
|
||||
kapt.include.compile.classpath=false
|
||||
|
||||
# Enable AndroidX
|
||||
android.useAndroidX=true
|
||||
|
||||
# Enable non-transitive R class namespacing where each library only contains
|
||||
# references to the resources it declares instead of declarations plus all
|
||||
# transitive dependency references.
|
||||
android.namespacedRClass=true
|
||||
android.nonTransitiveRClass=true
|
||||
|
||||
# Experimental AGP flags
|
||||
# Generate compile-time only R class for app modules.
|
||||
android.enableAppCompileTimeRClass=true
|
||||
# Keep AAPT2 daemons alive between incremental builds.
|
||||
android.keepWorkerActionServicesBetweenBuilds=true
|
||||
# Make R fields non-final to improve build speeds.
|
||||
# http://tools.android.com/tips/non-constant-fields
|
||||
android.nonFinalResIds=true
|
||||
# Enable the newly refactored resource shrinker.
|
||||
android.experimental.enableNewResourceShrinker=true
|
||||
# Enable precise shrinking in the new resource shrinker.
|
||||
android.experimental.enableNewResourceShrinker.preciseShrinking=true
|
||||
# Generate manifest class as a .class directly rather than a Java source file.
|
||||
android.generateManifestClass=true
|
||||
# Generate the text map of source sets and absolute paths to allow
|
||||
# generating relative paths from absolute paths later in the build.
|
||||
android.experimental.enableSourceSetPathsMap=true
|
||||
# Use relative paths for better Gradle caching of library build tasks
|
||||
android.experimental.cacheCompileLibResources=true
|
||||
|
||||
# Default Android build features
|
||||
# Disable BuildConfig generation by default
|
||||
android.defaults.buildfeatures.buildconfig=false
|
||||
# Disable AIDL stub generation by default
|
||||
android.defaults.buildfeatures.aidl=false
|
||||
# Disable RenderScript compilation by default
|
||||
android.defaults.buildfeatures.renderscript=false
|
||||
# Disable resource values generation by default in libraries
|
||||
android.defaults.buildfeatures.resvalues=false
|
||||
# Disable shader compilation by default
|
||||
android.defaults.buildfeatures.shaders=false
|
||||
# Disable Android resource processing by default
|
||||
android.library.defaults.buildfeatures.androidresources=false
|
||||
|
||||
# Suppress warnings for some features that aren't yet stabilized
|
||||
android.suppressUnsupportedOptionWarnings=android.enableR8.fullMode,android.useMinimalKeepRules,android.namespacedRClass,android.suppressUnsupportedOptionWarnings
|
||||
android.suppressUnsupportedOptionWarnings=android.keepWorkerActionServicesBetweenBuilds,\
|
||||
android.experimental.enableNewResourceShrinker,android.experimental.enableNewResourceShrinker.preciseShrinking,\
|
||||
android.enableAppCompileTimeRClass,android.experimental.enableSourceSetPathsMap,android.experimental.cacheCompileLibResources,\
|
||||
android.suppressUnsupportedOptionWarnings
|
||||
|
||||
# OSSRH sometimes struggles with slow deployments, so this makes Gradle
|
||||
# more tolerant to those delays.
|
||||
systemProp.org.gradle.internal.http.connectionTimeout=500000
|
||||
systemProp.org.gradle.internal.http.socketTimeout=500000
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+2
-2
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=11657af6356b7587bfb37287b5992e94a9686d5c8a0a1b60b87b9928a2decde5
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip
|
||||
distributionSha256Sum=b586e04868a22fd817c8971330fec37e298f3242eb85c374181b12d637f80302
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -17,67 +17,101 @@
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# 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"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
@@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
@@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
@@ -106,80 +140,95 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# 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
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
@@ -1,2 +1,28 @@
|
||||
pluginManagement {
|
||||
def agpVersion = "7.1.3"
|
||||
def kotlinVersion = "1.6.20"
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
plugins {
|
||||
id("com.android.application") version "$agpVersion"
|
||||
id("com.android.library") version "$agpVersion"
|
||||
id("org.jetbrains.kotlin.android") version "$kotlinVersion"
|
||||
id("org.jetbrains.kotlin.kapt") version "$kotlinVersion"
|
||||
}
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "wireguard-android"
|
||||
|
||||
include ':tunnel'
|
||||
include ':ui'
|
||||
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
set -ex
|
||||
curl -Lo - https://crowdin.com/backend/download/project/wireguard.zip | bsdtar -C ui/src/main/res -x -f - --strip-components 5 wireguard-android
|
||||
find ui/src/main/res -name strings.xml -exec bash -c '[[ $(xmllint --xpath "count(//resources/*)" {}) -ne 0 ]] || rm -rf "$(dirname {})"' \;
|
||||
+7
-7
@@ -1,17 +1,19 @@
|
||||
apply plugin: 'com.android.library'
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
}
|
||||
|
||||
version wireguardVersionName
|
||||
group groupName
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
compileSdkVersion 31
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
targetSdkVersion 31
|
||||
versionCode wireguardVersionCode
|
||||
versionName wireguardVersionName
|
||||
}
|
||||
@@ -20,9 +22,6 @@ android {
|
||||
path 'tools/CMakeLists.txt'
|
||||
}
|
||||
}
|
||||
libraryVariants.all {
|
||||
it.generateBuildConfigProvider.configure { enabled = false }
|
||||
}
|
||||
testOptions.unitTests.all {
|
||||
testLogging {
|
||||
events 'passed', 'skipped', 'failed', 'standardOut', 'standardError'
|
||||
@@ -46,13 +45,14 @@ android {
|
||||
}
|
||||
lintOptions {
|
||||
disable('LongLogTag')
|
||||
disable('NewApi') // Desugaring!
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "androidx.annotation:annotation:$annotationsVersion"
|
||||
implementation "androidx.collection:collection:$collectionVersion"
|
||||
implementation "com.google.code.findbugs:jsr305:$jsr305Version"
|
||||
compileOnly "com.google.code.findbugs:jsr305:$jsr305Version"
|
||||
testImplementation "junit:junit:$junitVersion"
|
||||
}
|
||||
|
||||
|
||||
+54
-48
@@ -1,53 +1,58 @@
|
||||
apply plugin: 'com.github.dcendents.android-maven'
|
||||
apply plugin: 'com.jfrog.bintray'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
install {
|
||||
repositories.mavenInstaller {
|
||||
pom.project {
|
||||
name 'WireGuard Tunnel Library'
|
||||
description 'Embeddable tunnel library for WireGuard for Android'
|
||||
url 'https://www.wireguard.com/'
|
||||
afterEvaluate {
|
||||
publishing {
|
||||
publications {
|
||||
release(MavenPublication) {
|
||||
groupId = groupName
|
||||
artifactId = 'tunnel'
|
||||
version wireguardVersionName
|
||||
|
||||
packaging 'aar'
|
||||
groupId groupName
|
||||
artifactId 'tunnel'
|
||||
version wireguardVersionName
|
||||
artifact sourcesJar
|
||||
artifact javadocJar
|
||||
|
||||
licenses {
|
||||
license {
|
||||
name 'The Apache Software License, Version 2.0'
|
||||
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||
distribution 'repo'
|
||||
from components.getByName("release")
|
||||
|
||||
pom {
|
||||
name = 'WireGuard Tunnel Library'
|
||||
description = 'Embeddable tunnel library for WireGuard for Android'
|
||||
url = 'https://www.wireguard.com/'
|
||||
|
||||
licenses {
|
||||
license {
|
||||
name = 'The Apache Software License, Version 2.0'
|
||||
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||
distribution = 'repo'
|
||||
}
|
||||
}
|
||||
scm {
|
||||
connection = 'scm:git:https://git.zx2c4.com/wireguard-android'
|
||||
developerConnection = 'scm:git:https://git.zx2c4.com/wireguard-android'
|
||||
url = 'https://git.zx2c4.com/wireguard-android'
|
||||
}
|
||||
developers {
|
||||
organization {
|
||||
name = 'WireGuard'
|
||||
url = 'https://www.wireguard.com/'
|
||||
}
|
||||
developer {
|
||||
name = 'WireGuard'
|
||||
email = 'team@wireguard.com'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
scm {
|
||||
connection 'scm:git:https://git.zx2c4.com/wireguard-android'
|
||||
url 'https://git.zx2c4.com/wireguard-android'
|
||||
}
|
||||
organization {
|
||||
name 'WireGuard'
|
||||
url 'https://www.wireguard.com/'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bintray {
|
||||
user = hasProperty('BINTRAY_USER') ? getProperty('BINTRAY_USER') : System.getenv('BINTRAY_USER')
|
||||
key = hasProperty('BINTRAY_KEY') ? getProperty('BINTRAY_KEY') : System.getenv('BINTRAY_KEY')
|
||||
|
||||
configurations = [ 'archives' ]
|
||||
|
||||
pkg {
|
||||
repo = 'wireguard-android'
|
||||
name = 'wireguard-android'
|
||||
userOrg = 'wireguard'
|
||||
licenses = [ 'Apache-2.0' ]
|
||||
vcsUrl = 'https://git.zx2c4.com/wireguard-android'
|
||||
publish = true
|
||||
|
||||
version {
|
||||
name = wireguardVersionName
|
||||
repositories {
|
||||
maven {
|
||||
name = "sonatype"
|
||||
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
|
||||
credentials {
|
||||
username = hasProperty('SONATYPE_USER') ? getProperty('SONATYPE_USER') : System.getenv('SONATYPE_USER')
|
||||
password = hasProperty('SONATYPE_PASSWORD') ? getProperty('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,9 +73,10 @@ android.libraryVariants.all { variant ->
|
||||
archiveClassifier = 'sources'
|
||||
from android.sourceSets.main.java.srcDirs
|
||||
}
|
||||
artifacts {
|
||||
archives sourcesJar
|
||||
archives javadocJar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signing {
|
||||
useGpgCmd()
|
||||
sign publishing.publications
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
~ Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
~ Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
<application>
|
||||
<service
|
||||
android:name="com.wireguard.android.backend.GoBackend$VpnService"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE">
|
||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService" />
|
||||
</intent-filter>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
@@ -55,6 +55,7 @@ public final class BackendException extends Exception {
|
||||
VPN_NOT_AUTHORIZED,
|
||||
UNABLE_TO_START_VPN,
|
||||
TUN_CREATION_ERROR,
|
||||
GO_ACTIVATION_ERROR_CODE
|
||||
GO_ACTIVATION_ERROR_CODE,
|
||||
DNS_RESOLUTION_FAILURE,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
@@ -16,6 +16,7 @@ import com.wireguard.android.backend.BackendException.Reason;
|
||||
import com.wireguard.android.backend.Tunnel.State;
|
||||
import com.wireguard.android.util.SharedLibraryLoader;
|
||||
import com.wireguard.config.Config;
|
||||
import com.wireguard.config.InetEndpoint;
|
||||
import com.wireguard.config.InetNetwork;
|
||||
import com.wireguard.config.Peer;
|
||||
import com.wireguard.crypto.Key;
|
||||
@@ -40,6 +41,7 @@ import androidx.collection.ArraySet;
|
||||
*/
|
||||
@NonNullForAll
|
||||
public final class GoBackend implements Backend {
|
||||
private static final int DNS_RESOLUTION_RETRIES = 10;
|
||||
private static final String TAG = "WireGuard/GoBackend";
|
||||
@Nullable private static AlwaysOnCallback alwaysOnCallback;
|
||||
private static GhettoCompletableFuture<VpnService> vpnService = new GhettoCompletableFuture<>();
|
||||
@@ -68,7 +70,7 @@ public final class GoBackend implements Backend {
|
||||
alwaysOnCallback = cb;
|
||||
}
|
||||
|
||||
private static native String wgGetConfig(int handle);
|
||||
@Nullable private static native String wgGetConfig(int handle);
|
||||
|
||||
private static native int wgGetSocketV4(int handle);
|
||||
|
||||
@@ -115,10 +117,11 @@ public final class GoBackend implements Backend {
|
||||
@Override
|
||||
public Statistics getStatistics(final Tunnel tunnel) {
|
||||
final Statistics stats = new Statistics();
|
||||
if (tunnel != currentTunnel) {
|
||||
if (tunnel != currentTunnel || currentTunnelHandle == -1)
|
||||
return stats;
|
||||
}
|
||||
final String config = wgGetConfig(currentTunnelHandle);
|
||||
if (config == null)
|
||||
return stats;
|
||||
Key key = null;
|
||||
long rx = 0;
|
||||
long tx = 0;
|
||||
@@ -214,8 +217,10 @@ public final class GoBackend implements Backend {
|
||||
throw new BackendException(Reason.VPN_NOT_AUTHORIZED);
|
||||
|
||||
final VpnService service;
|
||||
if (!vpnService.isDone())
|
||||
startVpnService();
|
||||
if (!vpnService.isDone()) {
|
||||
Log.d(TAG, "Requesting to start VpnService");
|
||||
context.startService(new Intent(context, VpnService.class));
|
||||
}
|
||||
|
||||
try {
|
||||
service = vpnService.get(2, TimeUnit.SECONDS);
|
||||
@@ -231,6 +236,25 @@ public final class GoBackend implements Backend {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
dnsRetry: for (int i = 0; i < DNS_RESOLUTION_RETRIES; ++i) {
|
||||
// Pre-resolve IPs so they're cached when building the userspace string
|
||||
for (final Peer peer : config.getPeers()) {
|
||||
final InetEndpoint ep = peer.getEndpoint().orElse(null);
|
||||
if (ep == null)
|
||||
continue;
|
||||
if (ep.getResolved().orElse(null) == null) {
|
||||
if (i < DNS_RESOLUTION_RETRIES - 1) {
|
||||
Log.w(TAG, "DNS host \"" + ep.getHost() + "\" failed to resolve; trying again");
|
||||
Thread.sleep(1000);
|
||||
continue dnsRetry;
|
||||
} else
|
||||
throw new BackendException(Reason.DNS_RESOLUTION_FAILURE, ep.getHost());
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Build config
|
||||
final String goConfig = config.toWgUserspaceString();
|
||||
|
||||
@@ -250,6 +274,9 @@ public final class GoBackend implements Backend {
|
||||
for (final InetAddress addr : config.getInterface().getDnsServers())
|
||||
builder.addDnsServer(addr.getHostAddress());
|
||||
|
||||
for (final String dnsSearchDomain : config.getInterface().getDnsSearchDomains())
|
||||
builder.addSearchDomain(dnsSearchDomain);
|
||||
|
||||
boolean sawDefaultRoute = false;
|
||||
for (final Peer peer : config.getPeers()) {
|
||||
for (final InetNetwork addr : peer.getAllowedIps()) {
|
||||
@@ -276,7 +303,7 @@ public final class GoBackend implements Backend {
|
||||
try (final ParcelFileDescriptor tun = builder.establish()) {
|
||||
if (tun == null)
|
||||
throw new BackendException(Reason.TUN_CREATION_ERROR);
|
||||
Log.d(TAG, "Go backend v" + wgVersion());
|
||||
Log.d(TAG, "Go backend " + wgVersion());
|
||||
currentTunnelHandle = wgTurnOn(tunnel.getName(), tun.detachFd(), goConfig);
|
||||
}
|
||||
if (currentTunnelHandle < 0)
|
||||
@@ -292,21 +319,16 @@ public final class GoBackend implements Backend {
|
||||
Log.w(TAG, "Tunnel already down");
|
||||
return;
|
||||
}
|
||||
|
||||
wgTurnOff(currentTunnelHandle);
|
||||
int handleToClose = currentTunnelHandle;
|
||||
currentTunnel = null;
|
||||
currentTunnelHandle = -1;
|
||||
currentConfig = null;
|
||||
wgTurnOff(handleToClose);
|
||||
}
|
||||
|
||||
tunnel.onStateChange(state);
|
||||
}
|
||||
|
||||
private void startVpnService() {
|
||||
Log.d(TAG, "Requesting to start VpnService");
|
||||
context.startService(new Intent(context, VpnService.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for {@link GoBackend} that is invoked when {@link VpnService} is started by the
|
||||
* system's Always-On VPN mode.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
@@ -56,9 +56,10 @@ public class Statistics {
|
||||
* @return a long representing the number of bytes received by this peer.
|
||||
*/
|
||||
public long peerRx(final Key peer) {
|
||||
if (!peerBytes.containsKey(peer))
|
||||
final Pair<Long, Long> rxTx = peerBytes.get(peer);
|
||||
if (rxTx == null)
|
||||
return 0;
|
||||
return peerBytes.get(peer).first;
|
||||
return rxTx.first;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,9 +70,10 @@ public class Statistics {
|
||||
* @return a long representing the number of bytes transmitted by this peer.
|
||||
*/
|
||||
public long peerTx(final Key peer) {
|
||||
if (!peerBytes.containsKey(peer))
|
||||
final Pair<Long, Long> rxTx = peerBytes.get(peer);
|
||||
if (rxTx == null)
|
||||
return 0;
|
||||
return peerBytes.get(peer).second;
|
||||
return rxTx.second;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
@@ -34,7 +34,8 @@ import java.util.Set;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* WireGuard backend that uses {@code wg-quick} to implement tunnel configuration.
|
||||
* Implementation of {@link Backend} that uses the kernel module and {@code wg-quick} to provide
|
||||
* WireGuard tunnels.
|
||||
*/
|
||||
|
||||
@NonNullForAll
|
||||
@@ -52,6 +53,10 @@ public final class WgQuickBackend implements Backend {
|
||||
this.toolsInstaller = toolsInstaller;
|
||||
}
|
||||
|
||||
public static boolean hasKernelSupport() {
|
||||
return new File("/sys/module/wireguard").exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getRunningTunnelNames() {
|
||||
final List<String> output = new ArrayList<>();
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2019-2020 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.system.OsConstants;
|
||||
import android.util.Base64;
|
||||
|
||||
import com.wireguard.android.util.RootShell.RootShellException;
|
||||
import com.wireguard.crypto.Ed25519;
|
||||
import com.wireguard.util.NonNullForAll;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Class that implements the logic for downloading and loading signed, prebuilt modules for
|
||||
* WireGuard into the running kernel.
|
||||
*/
|
||||
@NonNullForAll
|
||||
@SuppressWarnings("MagicNumber")
|
||||
public class ModuleLoader {
|
||||
private static final String MODULE_LIST_URL = "https://download.wireguard.com/android-module/modules.txt.sig";
|
||||
private static final String MODULE_NAME = "wireguard-%s.ko";
|
||||
private static final String MODULE_PUBLIC_KEY_BASE64 = "RWRmHuT9PSqtwfsLtEx+QS06BJtLgFYteL9WCNjH7yuyu5Y1DieSN7If";
|
||||
private static final String MODULE_URL = "https://download.wireguard.com/android-module/%s";
|
||||
private final File moduleDir;
|
||||
private final RootShell rootShell;
|
||||
private final File tmpDir;
|
||||
private final String userAgent;
|
||||
|
||||
/**
|
||||
* Public constructor for ModuleLoader
|
||||
*
|
||||
* @param context A {@link Context} instance.
|
||||
* @param rootShell A {@link RootShell} instance used to run elevated commands required for module
|
||||
* loading.
|
||||
* @param userAgent A {@link String} that represents the User-Agent string used for connections
|
||||
* to the upstream server.
|
||||
*/
|
||||
public ModuleLoader(final Context context, final RootShell rootShell, final String userAgent) {
|
||||
moduleDir = new File(context.getCacheDir(), "kmod");
|
||||
tmpDir = new File(context.getCacheDir(), "tmp");
|
||||
this.rootShell = rootShell;
|
||||
this.userAgent = userAgent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a WireGuard module is already loaded into the kernel.
|
||||
*
|
||||
* @return boolean indicating if WireGuard is already enabled in the kernel.
|
||||
*/
|
||||
public static boolean isModuleLoaded() {
|
||||
return new File("/sys/module/wireguard").exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Download the correct WireGuard module for the device
|
||||
*
|
||||
* @return {@link OsConstants}.EXIT_SUCCESS if everything succeeds, ENOENT otherwise.
|
||||
* @throws IOException if the remote hash list was not found or empty.
|
||||
* @throws RootShellException if {@link RootShell} has a failure executing elevated commands.
|
||||
* @throws NoSuchAlgorithmException if SHA256 algorithm is not available in device JDK.
|
||||
*/
|
||||
public Integer download() throws IOException, RootShellException, NoSuchAlgorithmException {
|
||||
final List<String> output = new ArrayList<>();
|
||||
rootShell.run(output, "sha256sum /proc/version|cut -d ' ' -f 1");
|
||||
if (output.size() != 1 || output.get(0).length() != 64)
|
||||
throw new InvalidParameterException("Invalid sha256 of /proc/version");
|
||||
final String moduleName = String.format(MODULE_NAME, output.get(0));
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(MODULE_LIST_URL).openConnection();
|
||||
connection.setRequestProperty("User-Agent", userAgent);
|
||||
connection.connect();
|
||||
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK)
|
||||
throw new IOException("Hash list could not be found");
|
||||
final byte[] input = new byte[1024 * 1024 * 3 /* 3MiB */];
|
||||
int len;
|
||||
try (final InputStream inputStream = connection.getInputStream()) {
|
||||
len = inputStream.read(input);
|
||||
}
|
||||
if (len <= 0)
|
||||
throw new IOException("Hash list was empty");
|
||||
final Map<String, Sha256Digest> modules = verifySignedHashes(new String(input, 0, len, StandardCharsets.UTF_8));
|
||||
if (modules == null)
|
||||
throw new InvalidParameterException("The signature did not verify or invalid hash list format");
|
||||
if (!modules.containsKey(moduleName))
|
||||
return OsConstants.ENOENT;
|
||||
connection = (HttpURLConnection) new URL(String.format(MODULE_URL, moduleName)).openConnection();
|
||||
connection.setRequestProperty("User-Agent", userAgent);
|
||||
connection.connect();
|
||||
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK)
|
||||
throw new IOException("Module file could not be found, despite being on hash list");
|
||||
|
||||
tmpDir.mkdirs();
|
||||
moduleDir.mkdir();
|
||||
File tempFile = null;
|
||||
try {
|
||||
tempFile = File.createTempFile("UNVERIFIED-", null, tmpDir);
|
||||
final MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
try (final InputStream inputStream = connection.getInputStream();
|
||||
final FileOutputStream outputStream = new FileOutputStream(tempFile)) {
|
||||
int total = 0;
|
||||
while ((len = inputStream.read(input)) > 0) {
|
||||
total += len;
|
||||
if (total > 1024 * 1024 * 15 /* 15 MiB */)
|
||||
throw new IOException("File too big");
|
||||
outputStream.write(input, 0, len);
|
||||
digest.update(input, 0, len);
|
||||
}
|
||||
outputStream.getFD().sync();
|
||||
}
|
||||
if (!Arrays.equals(digest.digest(), modules.get(moduleName).bytes))
|
||||
throw new IOException("Incorrect file hash");
|
||||
|
||||
if (!tempFile.renameTo(new File(moduleDir, moduleName)))
|
||||
throw new IOException("Unable to rename to final destination");
|
||||
} finally {
|
||||
if (tempFile != null)
|
||||
tempFile.delete();
|
||||
}
|
||||
return OsConstants.EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the downloaded module. ModuleLoader#download must be called before this.
|
||||
*
|
||||
* @throws IOException if {@link RootShell} has a failure executing elevated commands.
|
||||
* @throws RootShellException if {@link RootShell} has a failure executing elevated commands.
|
||||
*/
|
||||
public void loadModule() throws IOException, RootShellException {
|
||||
rootShell.run(null, String.format("insmod \"%s/wireguard-$(sha256sum /proc/version|cut -d ' ' -f 1).ko\"", moduleDir.getAbsolutePath()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the module might already exist in the app's data.
|
||||
*
|
||||
* @return boolean indicating whether downloadable module might exist already.
|
||||
*/
|
||||
public boolean moduleMightExist() {
|
||||
return moduleDir.exists() && moduleDir.isDirectory();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Map<String, Sha256Digest> verifySignedHashes(final String signifyDigest) {
|
||||
byte[] publicKeyBytes = Base64.decode(MODULE_PUBLIC_KEY_BASE64, Base64.DEFAULT);
|
||||
|
||||
if (publicKeyBytes == null || publicKeyBytes.length != 32 + 10 || publicKeyBytes[0] != 'E' || publicKeyBytes[1] != 'd')
|
||||
return null;
|
||||
|
||||
final String[] lines = signifyDigest.split("\n", 3);
|
||||
if (lines.length != 3)
|
||||
return null;
|
||||
if (!lines[0].startsWith("untrusted comment: "))
|
||||
return null;
|
||||
|
||||
byte[] signatureBytes = Base64.decode(lines[1], Base64.DEFAULT);
|
||||
if (signatureBytes == null || signatureBytes.length != 64 + 10)
|
||||
return null;
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
if (signatureBytes[i] != publicKeyBytes[i])
|
||||
return null;
|
||||
}
|
||||
publicKeyBytes = Arrays.copyOfRange(publicKeyBytes, 10, 10 + 32);
|
||||
signatureBytes = Arrays.copyOfRange(signatureBytes, 10, 10 + 64);
|
||||
if (!Ed25519.verify(lines[2].getBytes(StandardCharsets.UTF_8), signatureBytes, publicKeyBytes))
|
||||
return null;
|
||||
|
||||
final Map<String, Sha256Digest> hashes = new HashMap<>();
|
||||
for (final String line : lines[2].split("\n")) {
|
||||
final String[] components = line.split(" {2}", 2);
|
||||
if (components.length != 2)
|
||||
return null;
|
||||
try {
|
||||
hashes.put(components[1], new Sha256Digest(components[0]));
|
||||
} catch (final Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return hashes;
|
||||
}
|
||||
|
||||
private static final class Sha256Digest {
|
||||
private final byte[] bytes;
|
||||
|
||||
private Sha256Digest(final String hex) {
|
||||
if (hex.length() != 64)
|
||||
throw new InvalidParameterException("SHA256 hashes must be 32 bytes long");
|
||||
bytes = new byte[32];
|
||||
for (int i = 0; i < 32; ++i)
|
||||
bytes[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
@@ -43,8 +43,11 @@ public class RootShell {
|
||||
public RootShell(final Context context) {
|
||||
localBinaryDir = new File(context.getCodeCacheDir(), "bin");
|
||||
localTemporaryDir = new File(context.getCacheDir(), "tmp");
|
||||
preamble = String.format("export CALLING_PACKAGE=%s PATH=\"%s:$PATH\" TMPDIR='%s'; id -u\n",
|
||||
context.getPackageName(), localBinaryDir, localTemporaryDir);
|
||||
final String packageName = context.getPackageName();
|
||||
if (packageName.contains("'"))
|
||||
throw new RuntimeException("Impossibly invalid package name contains a single quote");
|
||||
preamble = String.format("export CALLING_PACKAGE=%s PATH=\"%s:$PATH\" TMPDIR='%s'; magisk --sqlite \"UPDATE policies SET notification=0, logging=0 WHERE package_name='%s'\" >/dev/null 2>&1; id -u\n",
|
||||
packageName, localBinaryDir, localTemporaryDir, packageName);
|
||||
}
|
||||
|
||||
private static boolean isExecutableInPath(final String name) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
@@ -143,9 +143,9 @@ public final class ToolsInstaller {
|
||||
extract();
|
||||
final StringBuilder script = new StringBuilder("set -ex; ");
|
||||
|
||||
script.append("trap 'rm -rf /data/adb/moduleswireguard' INT TERM EXIT; ");
|
||||
script.append("trap 'rm -rf /data/adb/modules/wireguard' INT TERM EXIT; ");
|
||||
script.append(String.format("rm -rf /data/adb/modules/wireguard/; mkdir -p /data/adb/modules/wireguard%s; ", INSTALL_DIR));
|
||||
script.append("printf 'name=WireGuard Command Line Tools\nversion=1.0\nversionCode=1\nauthor=zx2c4\ndescription=Command line tools for WireGuard\nminMagisk=1500\n' > /data/adb/modules/wireguard/module.prop; ");
|
||||
script.append("printf 'id=wireguard\nname=WireGuard Command Line Tools\nversion=1.0\nversionCode=1\nauthor=zx2c4\ndescription=Command line tools for WireGuard\nminMagisk=1500\n' > /data/adb/modules/wireguard/module.prop; ");
|
||||
script.append("touch /data/adb/modules/wireguard/auto_mount; ");
|
||||
for (final String name : EXECUTABLES) {
|
||||
final File destination = new File("/data/adb/modules/wireguard" + INSTALL_DIR, name);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
@@ -23,6 +23,7 @@ import androidx.annotation.Nullable;
|
||||
public final class InetAddresses {
|
||||
@Nullable private static final Method PARSER_METHOD;
|
||||
private static final Pattern WONT_TOUCH_RESOLVER = Pattern.compile("^(((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?)|((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))$");
|
||||
private static final Pattern VALID_HOSTNAME = Pattern.compile("^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?(?:\\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?)*\\.?$");
|
||||
|
||||
static {
|
||||
Method m = null;
|
||||
@@ -38,6 +39,16 @@ public final class InetAddresses {
|
||||
private InetAddresses() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether input is a valid DNS hostname.
|
||||
*
|
||||
* @param maybeHostname a string that is possibly a DNS hostname
|
||||
* @return whether or not maybeHostname is a valid DNS hostname
|
||||
*/
|
||||
public static boolean isHostname(final CharSequence maybeHostname) {
|
||||
return VALID_HOSTNAME.matcher(maybeHostname).matches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a numeric IPv4 or IPv6 address without performing any DNS lookups.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
@@ -40,6 +40,7 @@ public final class Interface {
|
||||
|
||||
private final Set<InetNetwork> addresses;
|
||||
private final Set<InetAddress> dnsServers;
|
||||
private final Set<String> dnsSearchDomains;
|
||||
private final Set<String> excludedApplications;
|
||||
private final Set<String> includedApplications;
|
||||
private final KeyPair keyPair;
|
||||
@@ -50,6 +51,7 @@ public final class Interface {
|
||||
// Defensively copy to ensure immutability even if the Builder is reused.
|
||||
addresses = Collections.unmodifiableSet(new LinkedHashSet<>(builder.addresses));
|
||||
dnsServers = Collections.unmodifiableSet(new LinkedHashSet<>(builder.dnsServers));
|
||||
dnsSearchDomains = Collections.unmodifiableSet(new LinkedHashSet<>(builder.dnsSearchDomains));
|
||||
excludedApplications = Collections.unmodifiableSet(new LinkedHashSet<>(builder.excludedApplications));
|
||||
includedApplications = Collections.unmodifiableSet(new LinkedHashSet<>(builder.includedApplications));
|
||||
keyPair = Objects.requireNonNull(builder.keyPair, "Interfaces must have a private key");
|
||||
@@ -108,6 +110,7 @@ public final class Interface {
|
||||
final Interface other = (Interface) obj;
|
||||
return addresses.equals(other.addresses)
|
||||
&& dnsServers.equals(other.dnsServers)
|
||||
&& dnsSearchDomains.equals(other.dnsSearchDomains)
|
||||
&& excludedApplications.equals(other.excludedApplications)
|
||||
&& includedApplications.equals(other.includedApplications)
|
||||
&& keyPair.equals(other.keyPair)
|
||||
@@ -135,6 +138,16 @@ public final class Interface {
|
||||
return dnsServers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of DNS search domains associated with the interface.
|
||||
*
|
||||
* @return a set of strings
|
||||
*/
|
||||
public Set<String> getDnsSearchDomains() {
|
||||
// The collection is already immutable.
|
||||
return dnsSearchDomains;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of applications excluded from using the interface.
|
||||
*
|
||||
@@ -222,6 +235,7 @@ public final class Interface {
|
||||
sb.append("Address = ").append(Attribute.join(addresses)).append('\n');
|
||||
if (!dnsServers.isEmpty()) {
|
||||
final List<String> dnsServerStrings = dnsServers.stream().map(InetAddress::getHostAddress).collect(Collectors.toList());
|
||||
dnsServerStrings.addAll(dnsSearchDomains);
|
||||
sb.append("DNS = ").append(Attribute.join(dnsServerStrings)).append('\n');
|
||||
}
|
||||
if (!excludedApplications.isEmpty())
|
||||
@@ -254,6 +268,8 @@ public final class Interface {
|
||||
// Defaults to an empty set.
|
||||
private final Set<InetAddress> dnsServers = new LinkedHashSet<>();
|
||||
// Defaults to an empty set.
|
||||
private final Set<String> dnsSearchDomains = new LinkedHashSet<>();
|
||||
// Defaults to an empty set.
|
||||
private final Set<String> excludedApplications = new LinkedHashSet<>();
|
||||
// Defaults to an empty set.
|
||||
private final Set<String> includedApplications = new LinkedHashSet<>();
|
||||
@@ -284,6 +300,16 @@ public final class Interface {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addDnsSearchDomain(final String dnsSearchDomain) {
|
||||
dnsSearchDomains.add(dnsSearchDomain);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addDnsSearchDomains(final Collection<String> dnsSearchDomains) {
|
||||
this.dnsSearchDomains.addAll(dnsSearchDomains);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Interface build() throws BadConfigException {
|
||||
if (keyPair == null)
|
||||
throw new BadConfigException(Section.INTERFACE, Location.PRIVATE_KEY,
|
||||
@@ -326,8 +352,15 @@ public final class Interface {
|
||||
|
||||
public Builder parseDnsServers(final CharSequence dnsServers) throws BadConfigException {
|
||||
try {
|
||||
for (final String dnsServer : Attribute.split(dnsServers))
|
||||
addDnsServer(InetAddresses.parse(dnsServer));
|
||||
for (final String dnsServer : Attribute.split(dnsServers)) {
|
||||
try {
|
||||
addDnsServer(InetAddresses.parse(dnsServer));
|
||||
} catch (final ParseException e) {
|
||||
if (e.getParsingClass() != InetAddress.class || !InetAddresses.isHostname(dnsServer))
|
||||
throw e;
|
||||
addDnsSearchDomain(dnsServer);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
} catch (final ParseException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.DNS, e);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright © 2016 Southern Storm Software, Pty Ltd.
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.util.Arrays;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Implementation of the Curve25519 elliptic curve algorithm.
|
||||
* Implementation of Curve25519 ECDH.
|
||||
* <p>
|
||||
* This implementation was imported to WireGuard from noise-java:
|
||||
* https://github.com/rweather/noise-java
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[Interface]
|
||||
Address = 192.0.2.2/32,2001:db8:ffff:ffff:ffff:ffff:ffff:ffff/128
|
||||
DNS = 192.0.2.0,yes
|
||||
DNS = 192.0.2.0,invalid_value
|
||||
PrivateKey = TFlmmEUC7V7VtiDYLKsbP5rySTKLIZq1yn8lMqK83wo=
|
||||
[Peer]
|
||||
AllowedIPs = 0.0.0.0/0, ::0/0
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
# Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
project("WireGuard")
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
|
||||
|
||||
# Work around https://github.com/android-ndk/ndk/issues/602
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
# Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
BUILDDIR ?= $(CURDIR)/build
|
||||
DESTDIR ?= $(CURDIR)/out
|
||||
@@ -20,11 +20,12 @@ export GOARCH := $(NDK_GO_ARCH_MAP_$(ANDROID_ARCH_NAME))
|
||||
export GOOS := android
|
||||
export CGO_ENABLED := 1
|
||||
|
||||
GO_VERSION := 1.15.2
|
||||
GO_VERSION := 1.18.2
|
||||
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 := 9bd39600d9fa1fa4a5ccce8761d249f7421cffe671376f791293c4138f3d7c62
|
||||
GO_HASH_linux-amd64 := b49fda1ca29a1946d6bb2a5a6982cf07ccd2aba849289508ee0f9918f6bb4552
|
||||
GO_HASH_darwin-amd64 := 1f5f539ce0baa8b65f196ee219abf73a7d9cf558ba9128cc0fe4833da18b04f2
|
||||
GO_HASH_darwin-arm64 := 6c7df9a2405f09aa9bab55c93c9c4ce41d3e58127d626bc1825ba5d0a0045d5c
|
||||
GO_HASH_linux-amd64 := e54bec97a1a5d230fc2f9ad0880fcbabb5888f30ed9666eca4a91c5a32e86cbc
|
||||
|
||||
default: $(DESTDIR)/libwg-go.so
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Copyright (C) 2017-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* Copyright © 2017-2022 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package main
|
||||
@@ -10,14 +10,13 @@ package main
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"log"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
@@ -29,13 +28,21 @@ import (
|
||||
)
|
||||
|
||||
type AndroidLogger struct {
|
||||
level C.int
|
||||
interfaceName string
|
||||
level C.int
|
||||
tag *C.char
|
||||
}
|
||||
|
||||
func (l AndroidLogger) Write(p []byte) (int, error) {
|
||||
C.__android_log_write(l.level, C.CString("WireGuard/GoBackend/"+l.interfaceName), C.CString(string(p)))
|
||||
return len(p), nil
|
||||
func cstring(s string) *C.char {
|
||||
b, err := unix.BytePtrFromString(s)
|
||||
if err != nil {
|
||||
b := [1]C.char{}
|
||||
return &b[0]
|
||||
}
|
||||
return (*C.char)(unsafe.Pointer(b))
|
||||
}
|
||||
|
||||
func (l AndroidLogger) Printf(format string, args ...interface{}) {
|
||||
C.__android_log_write(l.level, l.tag, cstring(fmt.Sprintf(format, args...)))
|
||||
}
|
||||
|
||||
type TunnelHandle struct {
|
||||
@@ -46,7 +53,6 @@ type TunnelHandle struct {
|
||||
var tunnelHandles map[int32]TunnelHandle
|
||||
|
||||
func init() {
|
||||
device.RoamingDisabled = true
|
||||
tunnelHandles = make(map[int32]TunnelHandle)
|
||||
signals := make(chan os.Signal)
|
||||
signal.Notify(signals, unix.SIGUSR2)
|
||||
@@ -56,52 +62,52 @@ func init() {
|
||||
select {
|
||||
case <-signals:
|
||||
n := runtime.Stack(buf, true)
|
||||
if n == len(buf) {
|
||||
n--
|
||||
}
|
||||
buf[n] = 0
|
||||
C.__android_log_write(C.ANDROID_LOG_ERROR, C.CString("WireGuard/GoBackend/Stacktrace"), (*C.char)(unsafe.Pointer(&buf[0])))
|
||||
C.__android_log_write(C.ANDROID_LOG_ERROR, cstring("WireGuard/GoBackend/Stacktrace"), (*C.char)(unsafe.Pointer(&buf[0])))
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
//export wgTurnOn
|
||||
func wgTurnOn(ifnameRef string, tunFd int32, settings string) int32 {
|
||||
interfaceName := string([]byte(ifnameRef))
|
||||
|
||||
func wgTurnOn(interfaceName string, tunFd int32, settings string) int32 {
|
||||
tag := cstring("WireGuard/GoBackend/" + interfaceName)
|
||||
logger := &device.Logger{
|
||||
Debug: log.New(&AndroidLogger{level: C.ANDROID_LOG_DEBUG, interfaceName: interfaceName}, "", 0),
|
||||
Info: log.New(&AndroidLogger{level: C.ANDROID_LOG_INFO, interfaceName: interfaceName}, "", 0),
|
||||
Error: log.New(&AndroidLogger{level: C.ANDROID_LOG_ERROR, interfaceName: interfaceName}, "", 0),
|
||||
Verbosef: AndroidLogger{level: C.ANDROID_LOG_DEBUG, tag: tag}.Printf,
|
||||
Errorf: AndroidLogger{level: C.ANDROID_LOG_ERROR, tag: tag}.Printf,
|
||||
}
|
||||
|
||||
logger.Debug.Println("Debug log enabled")
|
||||
|
||||
tun, name, err := tun.CreateUnmonitoredTUNFromFD(int(tunFd))
|
||||
if err != nil {
|
||||
unix.Close(int(tunFd))
|
||||
logger.Error.Println(err)
|
||||
logger.Errorf("CreateUnmonitoredTUNFromFD: %v", err)
|
||||
return -1
|
||||
}
|
||||
|
||||
logger.Info.Println("Attaching to interface", name)
|
||||
device := device.NewDevice(tun, logger)
|
||||
logger.Verbosef("Attaching to interface %v", name)
|
||||
device := device.NewDevice(tun, conn.NewStdNetBind(), logger)
|
||||
|
||||
setError := device.IpcSetOperation(bufio.NewReader(strings.NewReader(settings)))
|
||||
if setError != nil {
|
||||
err = device.IpcSet(settings)
|
||||
if err != nil {
|
||||
unix.Close(int(tunFd))
|
||||
logger.Error.Println(setError)
|
||||
logger.Errorf("IpcSet: %v", err)
|
||||
return -1
|
||||
}
|
||||
device.DisableSomeRoamingForBrokenMobileSemantics()
|
||||
|
||||
var uapi net.Listener
|
||||
|
||||
uapiFile, err := ipc.UAPIOpen(name)
|
||||
if err != nil {
|
||||
logger.Error.Println(err)
|
||||
logger.Errorf("UAPIOpen: %v", err)
|
||||
} else {
|
||||
uapi, err = ipc.UAPIListen(name, uapiFile)
|
||||
if err != nil {
|
||||
uapiFile.Close()
|
||||
logger.Error.Println(err)
|
||||
logger.Errorf("UAPIListen: %v", err)
|
||||
} else {
|
||||
go func() {
|
||||
for {
|
||||
@@ -115,8 +121,14 @@ func wgTurnOn(ifnameRef string, tunFd int32, settings string) int32 {
|
||||
}
|
||||
}
|
||||
|
||||
device.Up()
|
||||
logger.Info.Println("Device started")
|
||||
err = device.Up()
|
||||
if err != nil {
|
||||
logger.Errorf("Unable to bring up device: %v", err)
|
||||
uapiFile.Close()
|
||||
device.Close()
|
||||
return -1
|
||||
}
|
||||
logger.Verbosef("Device started")
|
||||
|
||||
var i int32
|
||||
for i = 0; i < math.MaxInt32; i++ {
|
||||
@@ -125,7 +137,9 @@ func wgTurnOn(ifnameRef string, tunFd int32, settings string) int32 {
|
||||
}
|
||||
}
|
||||
if i == math.MaxInt32 {
|
||||
unix.Close(int(tunFd))
|
||||
logger.Errorf("Unable to find empty handle")
|
||||
uapiFile.Close()
|
||||
device.Close()
|
||||
return -1
|
||||
}
|
||||
tunnelHandles[i] = TunnelHandle{device: device, uapi: uapi}
|
||||
@@ -185,19 +199,29 @@ func wgGetConfig(tunnelHandle int32) *C.char {
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
settings := new(bytes.Buffer)
|
||||
writer := bufio.NewWriter(settings)
|
||||
err := handle.device.IpcGetOperation(writer)
|
||||
settings, err := handle.device.IpcGet()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
writer.Flush()
|
||||
return C.CString(settings.String())
|
||||
return C.CString(settings)
|
||||
}
|
||||
|
||||
//export wgVersion
|
||||
func wgVersion() *C.char {
|
||||
return C.CString(device.WireGuardGoVersion)
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return C.CString("unknown")
|
||||
}
|
||||
for _, dep := range info.Deps {
|
||||
if dep.Path == "golang.zx2c4.com/wireguard" {
|
||||
parts := strings.Split(dep.Version, "-")
|
||||
if len(parts) == 3 && len(parts[2]) == 12 {
|
||||
return C.CString(parts[2][:7])
|
||||
}
|
||||
return C.CString(dep.Version)
|
||||
}
|
||||
}
|
||||
return C.CString("unknown")
|
||||
}
|
||||
|
||||
func main() {}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
module golang.zx2c4.com/wireguard/android
|
||||
|
||||
go 1.14
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 // indirect
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 // indirect
|
||||
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980
|
||||
golang.zx2c4.com/wireguard v0.0.20200321-0.20200622004228-b84f1d4db25e
|
||||
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/crypto v0.0.0-20220516162934-403b01795ae8 // indirect
|
||||
golang.org/x/net v0.0.0-20220516155154-20f960328961 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||
)
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y=
|
||||
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.zx2c4.com/wireguard v0.0.20200321-0.20200622004228-b84f1d4db25e h1:f8BS3yEMeIGx/zzJfihxDRedx6lT7EiJlfih4j6LY98=
|
||||
golang.zx2c4.com/wireguard v0.0.20200321-0.20200622004228-b84f1d4db25e/go.mod h1:GJvYs5O24/ASlwPiRklVnjMx2xQzrOic0DuU6GvYJL4=
|
||||
golang.org/x/crypto v0.0.0-20220516162934-403b01795ae8 h1:y+mHpWoQJNAHt26Nhh6JP7hvM71IRZureyvZhoVALIs=
|
||||
golang.org/x/crypto v0.0.0-20220516162934-403b01795ae8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/net v0.0.0-20220516155154-20f960328961 h1:+W/iTMPG0EL7aW+/atntZwZrvSRIj3m3yX414dSULUU=
|
||||
golang.org/x/net v0.0.0-20220516155154-20f960328961/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a h1:N2T1jUrTQE9Re6TFF5PhvEHXHCguynGhKjWVsIUt5cY=
|
||||
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d h1:q4JksJ2n0fmbXC0Aj0eOs6E0AcPqnKglxWXWFqGD6x0=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
From 1d1ba1da11afd73008c0e942db7621697055a6b6 Mon Sep 17 00:00:00 2001
|
||||
From b83553d9f260ba20c6faaa52e6fe6f74309eb41a Mon Sep 17 00:00:00 2001
|
||||
From: "Jason A. Donenfeld" <Jason@zx2c4.com>
|
||||
Date: Tue, 15 Sep 2020 13:39:22 +0200
|
||||
Date: Mon, 22 Feb 2021 02:36:03 +0100
|
||||
Subject: [PATCH] runtime: use CLOCK_BOOTTIME in nanotime on Linux
|
||||
|
||||
This makes timers account for having expired while a computer was
|
||||
@@ -28,10 +28,10 @@ Change-Id: I7b2a6ca0c5bc5fce57ec0eeafe7b68270b429321
|
||||
8 files changed, 11 insertions(+), 11 deletions(-)
|
||||
|
||||
diff --git a/src/runtime/sys_linux_386.s b/src/runtime/sys_linux_386.s
|
||||
index 5b9b638ad7..448ad8b2e6 100644
|
||||
index 1e3a834812..78b6021fc7 100644
|
||||
--- a/src/runtime/sys_linux_386.s
|
||||
+++ b/src/runtime/sys_linux_386.s
|
||||
@@ -339,13 +339,13 @@ noswitch:
|
||||
@@ -337,13 +337,13 @@ noswitch:
|
||||
|
||||
LEAL 8(SP), BX // &ts (struct timespec)
|
||||
MOVL BX, 4(SP)
|
||||
@@ -48,18 +48,18 @@ index 5b9b638ad7..448ad8b2e6 100644
|
||||
INVOKE_SYSCALL
|
||||
|
||||
diff --git a/src/runtime/sys_linux_amd64.s b/src/runtime/sys_linux_amd64.s
|
||||
index fe9c6bce85..4836a7c774 100644
|
||||
index 37cb8dad03..e8b730bcaa 100644
|
||||
--- a/src/runtime/sys_linux_amd64.s
|
||||
+++ b/src/runtime/sys_linux_amd64.s
|
||||
@@ -311,7 +311,7 @@ noswitch:
|
||||
MOVQ runtime·vdsoClockgettimeSym(SB), AX
|
||||
CMPQ AX, $0
|
||||
JEQ fallback
|
||||
@@ -302,7 +302,7 @@ noswitch:
|
||||
SUBQ $16, SP // Space for results
|
||||
ANDQ $~15, SP // Align for C code
|
||||
|
||||
- MOVL $1, DI // CLOCK_MONOTONIC
|
||||
+ MOVL $7, DI // CLOCK_BOOTTIME
|
||||
LEAQ 0(SP), SI
|
||||
CALL AX
|
||||
MOVQ 0(SP), AX // sec
|
||||
MOVQ runtime·vdsoClockgettimeSym(SB), AX
|
||||
CMPQ AX, $0
|
||||
diff --git a/src/runtime/sys_linux_arm.s b/src/runtime/sys_linux_arm.s
|
||||
index 475f52344c..bb567abcf4 100644
|
||||
--- a/src/runtime/sys_linux_arm.s
|
||||
@@ -105,10 +105,10 @@ index 198a5bacef..9715387f36 100644
|
||||
CBZ R2, fallback
|
||||
|
||||
diff --git a/src/runtime/sys_linux_mips64x.s b/src/runtime/sys_linux_mips64x.s
|
||||
index afad056d06..2c9162b903 100644
|
||||
index c3e9f37694..e3879acd38 100644
|
||||
--- a/src/runtime/sys_linux_mips64x.s
|
||||
+++ b/src/runtime/sys_linux_mips64x.s
|
||||
@@ -304,7 +304,7 @@ noswitch:
|
||||
@@ -312,7 +312,7 @@ noswitch:
|
||||
AND $~15, R1 // Align for C code
|
||||
MOVV R1, R29
|
||||
|
||||
@@ -157,5 +157,5 @@ index c15a1d5364..f52c4d5098 100644
|
||||
MOVW $SYS_clock_gettime, R1
|
||||
SYSCALL
|
||||
--
|
||||
2.28.0
|
||||
2.30.1
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Copyright © 2017-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* Copyright © 2017-2021 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
*/
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: BSD
|
||||
*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: BSD
|
||||
*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
Submodule tunnel/tools/wireguard-tools updated: 265e81a344...c0b68d2eaf
+15
-8
@@ -1,6 +1,8 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'org.jetbrains.kotlin.kapt'
|
||||
}
|
||||
|
||||
version wireguardVersionName
|
||||
group groupName
|
||||
@@ -10,9 +12,12 @@ group groupName
|
||||
final def keystorePropertiesFile = rootProject.file("keystore.properties")
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildFeatures.dataBinding = true
|
||||
buildFeatures.viewBinding = true
|
||||
compileSdkVersion 31
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
dataBinding = true
|
||||
viewBinding = true
|
||||
}
|
||||
defaultConfig {
|
||||
applicationId 'com.wireguard.android'
|
||||
minSdkVersion 21
|
||||
@@ -52,6 +57,8 @@ android {
|
||||
}
|
||||
lintOptions {
|
||||
disable('LongLogTag')
|
||||
warning('MissingTranslation')
|
||||
warning('ImpliedQuantity')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,12 +71,11 @@ dependencies {
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:$coordinatorLayoutVersion"
|
||||
implementation "androidx.biometric:biometric:$biometricVersion"
|
||||
implementation "androidx.core:core-ktx:$coreKtxVersion"
|
||||
implementation "androidx.databinding:databinding-runtime:$agpVersion"
|
||||
implementation "androidx.fragment:fragment-ktx:$fragmentVersion"
|
||||
implementation "androidx.preference:preference-ktx:$preferenceVersion"
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleRuntimeKtxVersion"
|
||||
implementation "androidx.datastore:datastore-preferences:$datastoreVersion"
|
||||
implementation "com.github.material-components:material-components-android:$materialComponentsVersion"
|
||||
implementation "com.google.android.material:material:$materialComponentsVersion"
|
||||
implementation "com.journeyapps:zxing-android-embedded:$zxingEmbeddedVersion"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
|
||||
@@ -84,5 +90,6 @@ tasks.withType(JavaCompile) {
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8
|
||||
useIR = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">WireGuard β</string>
|
||||
<string name="app_name" translatable="false">WireGuard β</string>
|
||||
</resources>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
android:name="android.software.leanback"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:name="android.hardware.camera.any"
|
||||
android:required="false" />
|
||||
|
||||
<permission
|
||||
@@ -41,14 +41,14 @@
|
||||
|
||||
<activity
|
||||
android:name=".activity.TunnelToggleActivity"
|
||||
android:theme="@style/NoBackgroundTheme" />
|
||||
android:theme="@style/NoBackgroundTheme"
|
||||
android:excludeFromRecents="true"/>
|
||||
|
||||
<activity android:name=".activity.MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
@@ -56,6 +56,15 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activity.TvMainActivity"
|
||||
android:theme="@style/TvTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activity.SettingsActivity"
|
||||
android:label="@string/settings"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android
|
||||
@@ -12,15 +12,15 @@ import android.os.StrictMode.ThreadPolicy
|
||||
import android.os.StrictMode.VmPolicy
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.datastore.DataStore
|
||||
import androidx.datastore.preferences.Preferences
|
||||
import androidx.datastore.preferences.createDataStore
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||
import com.wireguard.android.backend.Backend
|
||||
import com.wireguard.android.backend.GoBackend
|
||||
import com.wireguard.android.backend.WgQuickBackend
|
||||
import com.wireguard.android.configStore.FileConfigStore
|
||||
import com.wireguard.android.model.TunnelManager
|
||||
import com.wireguard.android.util.ModuleLoader
|
||||
import com.wireguard.android.util.RootShell
|
||||
import com.wireguard.android.util.ToolsInstaller
|
||||
import com.wireguard.android.util.UserKnobs
|
||||
@@ -41,7 +41,6 @@ class Application : android.app.Application() {
|
||||
private val futureBackend = CompletableDeferred<Backend>()
|
||||
private val coroutineScope = CoroutineScope(Job() + Dispatchers.Main.immediate)
|
||||
private var backend: Backend? = null
|
||||
private lateinit var moduleLoader: ModuleLoader
|
||||
private lateinit var rootShell: RootShell
|
||||
private lateinit var preferencesDataStore: DataStore<Preferences>
|
||||
private lateinit var toolsInstaller: ToolsInstaller
|
||||
@@ -66,15 +65,7 @@ class Application : android.app.Application() {
|
||||
private suspend fun determineBackend(): Backend {
|
||||
var backend: Backend? = null
|
||||
var didStartRootShell = false
|
||||
if (!ModuleLoader.isModuleLoaded() && moduleLoader.moduleMightExist()) {
|
||||
try {
|
||||
rootShell.start()
|
||||
didStartRootShell = true
|
||||
moduleLoader.loadModule()
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
}
|
||||
if (!UserKnobs.disableKernelModule.first() && ModuleLoader.isModuleLoaded()) {
|
||||
if (UserKnobs.enableKernelModule.first() && WgQuickBackend.hasKernelSupport()) {
|
||||
try {
|
||||
if (!didStartRootShell)
|
||||
rootShell.start()
|
||||
@@ -99,8 +90,7 @@ class Application : android.app.Application() {
|
||||
super.onCreate()
|
||||
rootShell = RootShell(applicationContext)
|
||||
toolsInstaller = ToolsInstaller(applicationContext, rootShell)
|
||||
moduleLoader = ModuleLoader(applicationContext, rootShell, USER_AGENT)
|
||||
preferencesDataStore = applicationContext.createDataStore(name = "settings")
|
||||
preferencesDataStore = PreferenceDataStoreFactory.create { applicationContext.preferencesDataStoreFile("settings") }
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
coroutineScope.launch {
|
||||
AppCompatDelegate.setDefaultNightMode(
|
||||
@@ -139,9 +129,6 @@ class Application : android.app.Application() {
|
||||
@JvmStatic
|
||||
suspend fun getBackend() = get().futureBackend.await()
|
||||
|
||||
@JvmStatic
|
||||
fun getModuleLoader() = get().moduleLoader
|
||||
|
||||
@JvmStatic
|
||||
fun getRootShell() = get().rootShell
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.activity
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
@@ -19,6 +19,7 @@ import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.text.style.StyleSpan
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
@@ -113,7 +114,7 @@ class LogViewerActivity : AppCompatActivity() {
|
||||
val key = KeyPair().privateKey.toHex()
|
||||
LOGS[key] = rawLogLines.toString().toByteArray(Charsets.UTF_8)
|
||||
lastUri = Uri.parse("content://${BuildConfig.APPLICATION_ID}.exported-log/$key")
|
||||
val shareIntent = ShareCompat.IntentBuilder.from(this)
|
||||
val shareIntent = ShareCompat.IntentBuilder(this)
|
||||
.setType("text/plain")
|
||||
.setSubject(getString(R.string.log_export_subject))
|
||||
.setStream(lastUri)
|
||||
@@ -125,9 +126,9 @@ class LogViewerActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.log_viewer, menu)
|
||||
saveButton = menu?.findItem(R.id.save_log)
|
||||
saveButton = menu.findItem(R.id.save_log)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -146,12 +147,14 @@ class LogViewerActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private val downloadsFileSaver = DownloadsFileSaver(this)
|
||||
|
||||
private suspend fun saveLog() {
|
||||
var exception: Throwable? = null
|
||||
var outputFile: DownloadsFileSaver.DownloadsFile? = null
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
outputFile = DownloadsFileSaver.save(this@LogViewerActivity, "wireguard-log.txt", "text/plain", true)
|
||||
outputFile = downloadsFileSaver.save("wireguard-log.txt", "text/plain", true)
|
||||
outputFile?.outputStream?.write(rawLogLines.toString().toByteArray(Charsets.UTF_8))
|
||||
} catch (e: Throwable) {
|
||||
outputFile?.delete()
|
||||
@@ -177,7 +180,7 @@ class LogViewerActivity : AppCompatActivity() {
|
||||
process = try {
|
||||
builder.start()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, Log.getStackTraceString(e))
|
||||
return@withContext
|
||||
}
|
||||
val stdout = BufferedReader(InputStreamReader(process!!.inputStream, StandardCharsets.UTF_8))
|
||||
@@ -199,11 +202,7 @@ class LogViewerActivity : AppCompatActivity() {
|
||||
it.scrollToPosition(logLines.size - 1)
|
||||
}
|
||||
} else {
|
||||
/* TODO: I'd prefer for the next line to be:
|
||||
* logLines.lastOrNull()?.msg += "\n$line"
|
||||
* However, as of writing, that causes the kotlin compiler to freak out and crash, spewing bytecode.
|
||||
*/
|
||||
logLines.lastOrNull()?.apply { msg += "\n$line" }
|
||||
logLines.lastOrNull()?.msg += "\n$line"
|
||||
if (haveScrolled) logAdapter.notifyDataSetChanged()
|
||||
}
|
||||
if (!haveScrolled) {
|
||||
@@ -251,6 +250,7 @@ class LogViewerActivity : AppCompatActivity() {
|
||||
*/
|
||||
private val THREADTIME_LINE: Pattern = Pattern.compile("^(\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}.\\d{3})(?:\\s+[0-9A-Za-z]+)?\\s+(\\d+)\\s+(\\d+)\\s+([A-Z])\\s+(.+?)\\s*: (.*)$")
|
||||
private val LOGS: MutableMap<String, ByteArray> = ConcurrentHashMap()
|
||||
private const val TAG = "WireGuard/LogViewerActivity"
|
||||
}
|
||||
|
||||
private inner class LogEntryAdapter : RecyclerView.Adapter<LogEntryAdapter.ViewHolder>() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.activity
|
||||
@@ -12,6 +12,7 @@ import android.view.View
|
||||
import androidx.appcompat.app.ActionBar
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import androidx.fragment.app.commit
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.fragment.TunnelDetailFragment
|
||||
import com.wireguard.android.fragment.TunnelEditorFragment
|
||||
@@ -72,11 +73,11 @@ class MainActivity : BaseActivity(), FragmentManager.OnBackStackChangedListener
|
||||
true
|
||||
}
|
||||
R.id.menu_action_edit -> {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.detail_container, TunnelEditorFragment())
|
||||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
supportFragmentManager.commit {
|
||||
replace(R.id.detail_container, TunnelEditorFragment())
|
||||
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
|
||||
addToBackStack(null)
|
||||
}
|
||||
true
|
||||
}
|
||||
// This menu item is handled by the editor fragment.
|
||||
@@ -104,11 +105,11 @@ class MainActivity : BaseActivity(), FragmentManager.OnBackStackChangedListener
|
||||
fragmentManager.popBackStackImmediate()
|
||||
} else if (backStackEntries == 0) {
|
||||
// Create and show a new detail fragment.
|
||||
fragmentManager.beginTransaction()
|
||||
.add(R.id.detail_container, TunnelDetailFragment())
|
||||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
fragmentManager.commit {
|
||||
add(R.id.detail_container, TunnelDetailFragment())
|
||||
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
|
||||
addToBackStack(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.activity
|
||||
@@ -8,6 +8,7 @@ import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import androidx.fragment.app.commit
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
@@ -16,7 +17,6 @@ import com.wireguard.android.R
|
||||
import com.wireguard.android.backend.WgQuickBackend
|
||||
import com.wireguard.android.preference.PreferencesPreferenceDataStore
|
||||
import com.wireguard.android.util.AdminKnobs
|
||||
import com.wireguard.android.util.ModuleLoader
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -28,9 +28,9 @@ class SettingsActivity : ThemeChangeAwareActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (supportFragmentManager.findFragmentById(android.R.id.content) == null) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.add(android.R.id.content, SettingsFragment())
|
||||
.commit()
|
||||
supportFragmentManager.commit {
|
||||
add(android.R.id.content, SettingsFragment())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,30 +78,19 @@ class SettingsActivity : ThemeChangeAwareActivity() {
|
||||
startActivity(Intent(requireContext(), LogViewerActivity::class.java))
|
||||
true
|
||||
}
|
||||
val moduleInstaller = preferenceManager.findPreference<Preference>("module_downloader")
|
||||
val kernelModuleDisabler = preferenceManager.findPreference<Preference>("kernel_module_disabler")
|
||||
moduleInstaller?.isVisible = false
|
||||
if (ModuleLoader.isModuleLoaded()) {
|
||||
moduleInstaller?.parent?.removePreference(moduleInstaller)
|
||||
val kernelModuleEnabler = preferenceManager.findPreference<Preference>("kernel_module_enabler")
|
||||
if (WgQuickBackend.hasKernelSupport()) {
|
||||
lifecycleScope.launch {
|
||||
if (Application.getBackend() !is WgQuickBackend) {
|
||||
try {
|
||||
withContext(Dispatchers.IO) { Application.getRootShell().start() }
|
||||
} catch (_: Throwable) {
|
||||
kernelModuleDisabler?.parent?.removePreference(kernelModuleDisabler)
|
||||
kernelModuleEnabler?.parent?.removePreference(kernelModuleEnabler)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
kernelModuleDisabler?.parent?.removePreference(kernelModuleDisabler)
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
withContext(Dispatchers.IO) { Application.getRootShell().start() }
|
||||
moduleInstaller?.isVisible = true
|
||||
} catch (_: Throwable) {
|
||||
moduleInstaller?.parent?.removePreference(moduleInstaller)
|
||||
}
|
||||
}
|
||||
kernelModuleEnabler?.parent?.removePreference(kernelModuleEnabler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.activity
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.activity
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.commit
|
||||
import com.wireguard.android.fragment.TunnelEditorFragment
|
||||
import com.wireguard.android.model.ObservableTunnel
|
||||
|
||||
@@ -15,9 +16,9 @@ class TunnelCreatorActivity : BaseActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (supportFragmentManager.findFragmentById(android.R.id.content) == null) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.add(android.R.id.content, TunnelEditorFragment())
|
||||
.commit()
|
||||
supportFragmentManager.commit {
|
||||
add(android.R.id.content, TunnelEditorFragment())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.activity
|
||||
@@ -10,20 +10,23 @@ import android.os.Bundle
|
||||
import android.service.quicksettings.TileService
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.QuickTileService
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.backend.GoBackend
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
class TunnelToggleActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
private val permissionActivityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { toggleTunnelWithPermissionsResult() }
|
||||
|
||||
private fun toggleTunnelWithPermissionsResult() {
|
||||
val tunnel = Application.getTunnelManager().lastUsedTunnel ?: return
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
@@ -42,6 +45,20 @@ class TunnelToggleActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
lifecycleScope.launch {
|
||||
if (Application.getBackend() is GoBackend) {
|
||||
val intent = GoBackend.VpnService.prepare(this@TunnelToggleActivity)
|
||||
if (intent != null) {
|
||||
permissionActivityResultLauncher.launch(intent)
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
toggleTunnelWithPermissionsResult()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "WireGuard/TunnelToggleActivity"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,391 @@
|
||||
/*
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.activity
|
||||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.os.storage.StorageManager
|
||||
import android.os.storage.StorageVolume
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.view.forEach
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.databinding.ObservableBoolean
|
||||
import androidx.databinding.ObservableField
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.backend.GoBackend
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.android.databinding.Keyed
|
||||
import com.wireguard.android.databinding.ObservableKeyedArrayList
|
||||
import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter
|
||||
import com.wireguard.android.databinding.TvActivityBinding
|
||||
import com.wireguard.android.databinding.TvFileListItemBinding
|
||||
import com.wireguard.android.databinding.TvTunnelListItemBinding
|
||||
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 kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
class TvMainActivity : AppCompatActivity() {
|
||||
private val tunnelFileImportResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { data ->
|
||||
if (data == null) return@registerForActivityResult
|
||||
lifecycleScope.launch {
|
||||
TunnelImporter.importTunnel(contentResolver, data) {
|
||||
Toast.makeText(this@TvMainActivity, it, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
private var pendingTunnel: ObservableTunnel? = null
|
||||
private val permissionActivityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
val tunnel = pendingTunnel
|
||||
if (tunnel != null)
|
||||
setTunnelStateWithPermissionsResult(tunnel)
|
||||
pendingTunnel = null
|
||||
}
|
||||
|
||||
private fun setTunnelStateWithPermissionsResult(tunnel: ObservableTunnel) {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
tunnel.setStateAsync(Tunnel.State.TOGGLE)
|
||||
} catch (e: Throwable) {
|
||||
val error = ErrorMessages[e]
|
||||
val message = getString(R.string.error_up, error)
|
||||
Toast.makeText(this@TvMainActivity, message, Toast.LENGTH_LONG).show()
|
||||
Log.e(TAG, message, e)
|
||||
}
|
||||
updateStats()
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var binding: TvActivityBinding
|
||||
private val isDeleting = ObservableBoolean()
|
||||
private val files = ObservableKeyedArrayList<String, KeyedFile>()
|
||||
private val filesRoot = ObservableField("")
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = TvActivityBinding.inflate(layoutInflater)
|
||||
lifecycleScope.launch {
|
||||
binding.tunnels = Application.getTunnelManager().getTunnels()
|
||||
if (binding.tunnels?.isEmpty() == true)
|
||||
binding.importButton.requestFocus()
|
||||
else
|
||||
binding.tunnelList.requestFocus()
|
||||
}
|
||||
binding.isDeleting = isDeleting
|
||||
binding.files = files
|
||||
binding.filesRoot = filesRoot
|
||||
val gridManager = binding.tunnelList.layoutManager as GridLayoutManager
|
||||
gridManager.spanSizeLookup = SlatedSpanSizeLookup(gridManager)
|
||||
binding.tunnelRowConfigurationHandler = object : ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler<TvTunnelListItemBinding, ObservableTunnel> {
|
||||
override fun onConfigureRow(binding: TvTunnelListItemBinding, item: ObservableTunnel, position: Int) {
|
||||
binding.isDeleting = isDeleting
|
||||
binding.isFocused = ObservableBoolean()
|
||||
binding.root.setOnFocusChangeListener { _, focused ->
|
||||
binding.isFocused?.set(focused)
|
||||
}
|
||||
binding.root.setOnClickListener {
|
||||
lifecycleScope.launch {
|
||||
if (isDeleting.get()) {
|
||||
try {
|
||||
item.deleteAsync()
|
||||
if (this@TvMainActivity.binding.tunnels?.isEmpty() != false)
|
||||
isDeleting.set(false)
|
||||
} catch (e: Throwable) {
|
||||
val error = ErrorMessages[e]
|
||||
val message = getString(R.string.config_delete_error, error)
|
||||
Toast.makeText(this@TvMainActivity, message, Toast.LENGTH_LONG).show()
|
||||
Log.e(TAG, message, e)
|
||||
}
|
||||
} else {
|
||||
if (Application.getBackend() is GoBackend) {
|
||||
val intent = GoBackend.VpnService.prepare(binding.root.context)
|
||||
if (intent != null) {
|
||||
pendingTunnel = item
|
||||
permissionActivityResultLauncher.launch(intent)
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
setTunnelStateWithPermissionsResult(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.filesRowConfigurationHandler = object : ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler<TvFileListItemBinding, KeyedFile> {
|
||||
override fun onConfigureRow(binding: TvFileListItemBinding, item: KeyedFile, position: Int) {
|
||||
binding.root.setOnClickListener {
|
||||
if (item.file.isDirectory)
|
||||
navigateTo(item.file)
|
||||
else {
|
||||
val uri = Uri.fromFile(item.file)
|
||||
files.clear()
|
||||
filesRoot.set("")
|
||||
lifecycleScope.launch {
|
||||
TunnelImporter.importTunnel(contentResolver, uri) {
|
||||
Toast.makeText(this@TvMainActivity, it, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
runOnUiThread {
|
||||
this@TvMainActivity.binding.tunnelList.requestFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.importButton.setOnClickListener {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
if (filesRoot.get()?.isEmpty() != false) {
|
||||
navigateTo(File("/"))
|
||||
runOnUiThread {
|
||||
binding.filesList.requestFocus()
|
||||
}
|
||||
} else {
|
||||
files.clear()
|
||||
filesRoot.set("")
|
||||
runOnUiThread {
|
||||
binding.tunnelList.requestFocus()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
tunnelFileImportResultLauncher.launch("*/*")
|
||||
} catch (_: Throwable) {
|
||||
Toast.makeText(this@TvMainActivity, getString(R.string.tv_no_file_picker), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.deleteButton.setOnClickListener {
|
||||
isDeleting.set(!isDeleting.get())
|
||||
runOnUiThread {
|
||||
binding.tunnelList.requestFocus()
|
||||
}
|
||||
}
|
||||
binding.executePendingBindings()
|
||||
setContentView(binding.root)
|
||||
|
||||
lifecycleScope.launch {
|
||||
while (true) {
|
||||
updateStats()
|
||||
delay(1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var pendingNavigation: File? = null
|
||||
private val permissionRequestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) {
|
||||
val to = pendingNavigation
|
||||
if (it && to != null)
|
||||
navigateTo(to)
|
||||
pendingNavigation = null
|
||||
}
|
||||
|
||||
private var cachedRoots: Collection<KeyedFile>? = null
|
||||
|
||||
private suspend fun makeStorageRoots(): Collection<KeyedFile> = withContext(Dispatchers.IO) {
|
||||
cachedRoots?.let { return@withContext it }
|
||||
val list = HashSet<KeyedFile>()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
val storageManager: StorageManager = getSystemService() ?: return@withContext list
|
||||
list.addAll(storageManager.storageVolumes.mapNotNull { volume ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
volume.directory?.let { KeyedFile(it, volume.getDescription(this@TvMainActivity)) }
|
||||
} else {
|
||||
KeyedFile((StorageVolume::class.java.getMethod("getPathFile").invoke(volume) as File), volume.getDescription(this@TvMainActivity))
|
||||
}
|
||||
})
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
list.add(KeyedFile(Environment.getExternalStorageDirectory()))
|
||||
try {
|
||||
File("/storage").listFiles()?.forEach {
|
||||
if (!it.isDirectory) return@forEach
|
||||
try {
|
||||
if (Environment.isExternalStorageRemovable(it)) {
|
||||
list.add(KeyedFile(it))
|
||||
}
|
||||
} catch (_: Throwable) {
|
||||
}
|
||||
}
|
||||
} catch (_: Throwable) {
|
||||
}
|
||||
}
|
||||
cachedRoots = list
|
||||
list
|
||||
}
|
||||
|
||||
private fun isBelowCachedRoots(maybeChild: File): Boolean {
|
||||
val cachedRoots = cachedRoots ?: return true
|
||||
for (root in cachedRoots) {
|
||||
if (maybeChild.canonicalPath.startsWith(root.file.canonicalPath))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun navigateTo(directory: File) {
|
||||
require(Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
|
||||
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||
pendingNavigation = directory
|
||||
permissionRequestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
return
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
if (isBelowCachedRoots(directory)) {
|
||||
val roots = makeStorageRoots()
|
||||
if (roots.count() == 1) {
|
||||
navigateTo(roots.first().file)
|
||||
return@launch
|
||||
}
|
||||
files.clear()
|
||||
files.addAll(roots)
|
||||
filesRoot.set(getString(R.string.tv_select_a_storage_drive))
|
||||
return@launch
|
||||
}
|
||||
|
||||
val newFiles = withContext(Dispatchers.IO) {
|
||||
val newFiles = ArrayList<KeyedFile>()
|
||||
try {
|
||||
directory.parentFile?.let {
|
||||
newFiles.add(KeyedFile(it, "../"))
|
||||
}
|
||||
val listing = directory.listFiles() ?: return@withContext null
|
||||
listing.forEach {
|
||||
if (it.extension == "conf" || it.extension == "zip" || it.isDirectory)
|
||||
newFiles.add(KeyedFile(it))
|
||||
}
|
||||
newFiles.sortWith { a, b ->
|
||||
if (a.file.isDirectory && !b.file.isDirectory) -1
|
||||
else if (!a.file.isDirectory && b.file.isDirectory) 1
|
||||
else a.file.compareTo(b.file)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, Log.getStackTraceString(e))
|
||||
}
|
||||
newFiles
|
||||
}
|
||||
if (newFiles?.isEmpty() != false)
|
||||
return@launch
|
||||
files.clear()
|
||||
files.addAll(newFiles)
|
||||
filesRoot.set(directory.canonicalPath)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
when {
|
||||
isDeleting.get() -> {
|
||||
isDeleting.set(false)
|
||||
runOnUiThread {
|
||||
binding.tunnelList.requestFocus()
|
||||
}
|
||||
}
|
||||
filesRoot.get()?.isNotEmpty() == true -> {
|
||||
files.clear()
|
||||
filesRoot.set("")
|
||||
runOnUiThread {
|
||||
binding.tunnelList.requestFocus()
|
||||
}
|
||||
}
|
||||
else -> super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateStats() {
|
||||
binding.tunnelList.forEach { viewItem ->
|
||||
val listItem = DataBindingUtil.findBinding<TvTunnelListItemBinding>(viewItem)
|
||||
?: return@forEach
|
||||
try {
|
||||
val tunnel = listItem.item!!
|
||||
if (tunnel.state != Tunnel.State.UP || isDeleting.get()) {
|
||||
throw Exception()
|
||||
}
|
||||
val statistics = tunnel.getStatisticsAsync()
|
||||
val rx = statistics.totalRx()
|
||||
val tx = statistics.totalTx()
|
||||
listItem.tunnelTransfer.text = getString(R.string.transfer_rx_tx, QuantityFormatter.formatBytes(rx), QuantityFormatter.formatBytes(tx))
|
||||
listItem.tunnelTransfer.visibility = View.VISIBLE
|
||||
} catch (_: Throwable) {
|
||||
listItem.tunnelTransfer.visibility = View.GONE
|
||||
listItem.tunnelTransfer.text = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class KeyedFile(val file: File, private val forcedKey: String? = null) : Keyed<String> {
|
||||
override val key: String
|
||||
get() = forcedKey ?: if (file.isDirectory) "${file.name}/" else file.name
|
||||
}
|
||||
|
||||
private class SlatedSpanSizeLookup(private val gridManager: GridLayoutManager) : SpanSizeLookup() {
|
||||
private val originalHeight = gridManager.spanCount
|
||||
private var newWidth = 0
|
||||
private lateinit var sizeMap: Array<IntArray?>
|
||||
|
||||
private fun emptyUnderIndex(index: Int, size: Int): Int {
|
||||
sizeMap[size - 1]?.let { return it[index] }
|
||||
val sizes = IntArray(size)
|
||||
val oh = originalHeight
|
||||
val nw = newWidth
|
||||
var empties = 0
|
||||
for (i in 0 until size) {
|
||||
val ox = (i + empties) / oh
|
||||
val oy = (i + empties) % oh
|
||||
var empty = 0
|
||||
for (j in oy + 1 until oh) {
|
||||
val ni = nw * j + ox
|
||||
if (ni < size)
|
||||
break
|
||||
empty++
|
||||
}
|
||||
empties += empty
|
||||
sizes[i] = empty
|
||||
}
|
||||
sizeMap[size - 1] = sizes
|
||||
return sizes[index]
|
||||
}
|
||||
|
||||
override fun getSpanSize(position: Int): Int {
|
||||
if (newWidth == 0) {
|
||||
val child = gridManager.getChildAt(0) ?: return 1
|
||||
if (child.width == 0) return 1
|
||||
newWidth = gridManager.width / child.width
|
||||
sizeMap = Array(originalHeight * newWidth - 1) { null }
|
||||
}
|
||||
val total = gridManager.itemCount
|
||||
if (total >= originalHeight * newWidth || total == 0)
|
||||
return 1
|
||||
return emptyUnderIndex(position, total) + 1
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "WireGuard/TvMainActivity"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.configStore
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.configStore
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.databinding
|
||||
@@ -152,6 +152,12 @@ object BindingAdapters {
|
||||
view.text = if (addresses != null) Attribute.join(addresses.map { it?.hostAddress }) else ""
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@BindingAdapter("android:text")
|
||||
fun setStringSetText(view: TextView, strings: Iterable<String?>?) {
|
||||
view.text = if (strings != null) Attribute.join(strings) else ""
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun tryParseInt(s: String?): Int {
|
||||
if (s == null)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.databinding
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.databinding
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.databinding
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.databinding
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.databinding
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.fragment
|
||||
@@ -41,7 +41,7 @@ class AddTunnelsSheet : BottomSheetDialogFragment() {
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
if (savedInstanceState != null) dismiss()
|
||||
val view = inflater.inflate(R.layout.add_tunnels_bottom_sheet, container, false)
|
||||
if (activity?.packageManager?.hasSystemFeature(PackageManager.FEATURE_CAMERA) != true) {
|
||||
if (activity?.packageManager?.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) != true) {
|
||||
val qrcode = view.findViewById<View>(R.id.create_from_qrcode)
|
||||
qrcode.isEnabled = false
|
||||
qrcode.visibility = View.GONE
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.fragment
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Dialog
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.widget.Button
|
||||
import android.widget.Toast
|
||||
@@ -40,12 +40,11 @@ class AppListDialogFragment : DialogFragment() {
|
||||
try {
|
||||
val applicationData: MutableList<ApplicationData> = ArrayList()
|
||||
withContext(Dispatchers.IO) {
|
||||
val launcherIntent = Intent(Intent.ACTION_MAIN, null)
|
||||
launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER)
|
||||
val resolveInfos = pm.queryIntentActivities(launcherIntent, 0)
|
||||
resolveInfos.forEach {
|
||||
val packageName = it.activityInfo.packageName
|
||||
val appData = ApplicationData(it.loadIcon(pm), it.loadLabel(pm).toString(), packageName, currentlySelectedApps.contains(packageName))
|
||||
val packageInfos = pm.getPackagesHoldingPermissions(arrayOf(Manifest.permission.INTERNET), 0)
|
||||
packageInfos.forEach {
|
||||
val packageName = it.packageName
|
||||
val appInfo = it.applicationInfo
|
||||
val appData = ApplicationData(appInfo.loadIcon(pm), appInfo.loadLabel(pm).toString(), packageName, currentlySelectedApps.contains(packageName))
|
||||
applicationData.add(appData)
|
||||
appData.addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {
|
||||
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.fragment
|
||||
@@ -31,7 +31,6 @@ import kotlinx.coroutines.launch
|
||||
* attached to a `BaseActivity`.
|
||||
*/
|
||||
abstract class BaseFragment : Fragment(), OnSelectedTunnelChangedListener {
|
||||
private var baseActivity: BaseActivity? = null
|
||||
private var pendingTunnel: ObservableTunnel? = null
|
||||
private var pendingTunnelUp: Boolean? = null
|
||||
private val permissionActivityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
@@ -44,24 +43,18 @@ abstract class BaseFragment : Fragment(), OnSelectedTunnelChangedListener {
|
||||
}
|
||||
|
||||
protected var selectedTunnel: ObservableTunnel?
|
||||
get() = baseActivity?.selectedTunnel
|
||||
get() = (activity as? BaseActivity)?.selectedTunnel
|
||||
protected set(tunnel) {
|
||||
baseActivity?.selectedTunnel = tunnel
|
||||
(activity as? BaseActivity)?.selectedTunnel = tunnel
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
if (context is BaseActivity) {
|
||||
baseActivity = context
|
||||
baseActivity?.addOnSelectedTunnelChangedListener(this)
|
||||
} else {
|
||||
baseActivity = null
|
||||
}
|
||||
(activity as? BaseActivity)?.addOnSelectedTunnelChangedListener(this)
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
baseActivity?.removeOnSelectedTunnelChangedListener(this)
|
||||
baseActivity = null
|
||||
(activity as? BaseActivity)?.removeOnSelectedTunnelChangedListener(this)
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
@@ -71,9 +64,10 @@ abstract class BaseFragment : Fragment(), OnSelectedTunnelChangedListener {
|
||||
is TunnelListItemBinding -> binding.item
|
||||
else -> return
|
||||
} ?: return
|
||||
lifecycleScope.launch {
|
||||
val activity = activity ?: return
|
||||
activity.lifecycleScope.launch {
|
||||
if (Application.getBackend() is GoBackend) {
|
||||
val intent = GoBackend.VpnService.prepare(view.context)
|
||||
val intent = GoBackend.VpnService.prepare(activity)
|
||||
if (intent != null) {
|
||||
pendingTunnel = tunnel
|
||||
pendingTunnelUp = checked
|
||||
@@ -86,20 +80,21 @@ abstract class BaseFragment : Fragment(), OnSelectedTunnelChangedListener {
|
||||
}
|
||||
|
||||
private fun setTunnelStateWithPermissionsResult(tunnel: ObservableTunnel, checked: Boolean) {
|
||||
lifecycleScope.launch {
|
||||
val activity = activity ?: return
|
||||
activity.lifecycleScope.launch {
|
||||
try {
|
||||
tunnel.setStateAsync(Tunnel.State.of(checked))
|
||||
} catch (e: Throwable) {
|
||||
val error = ErrorMessages[e]
|
||||
val messageResId = if (checked) R.string.error_up else R.string.error_down
|
||||
val message = requireContext().getString(messageResId, error)
|
||||
val message = activity.getString(messageResId, error)
|
||||
val view = view
|
||||
if (view != null)
|
||||
Snackbar.make(view, message, Snackbar.LENGTH_LONG)
|
||||
.setAnchorView(view.findViewById(R.id.create_fab))
|
||||
.show()
|
||||
else
|
||||
Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show()
|
||||
Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
|
||||
Log.e(TAG, message, e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.fragment
|
||||
@@ -29,15 +29,15 @@ class ConfigNamingDialogFragment : DialogFragment() {
|
||||
private var imm: InputMethodManager? = null
|
||||
|
||||
private fun createTunnelAndDismiss() {
|
||||
binding?.let {
|
||||
val name = it.tunnelNameText.text.toString()
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
Application.getTunnelManager().create(name, config)
|
||||
dismiss()
|
||||
} catch (e: Throwable) {
|
||||
it.tunnelNameTextLayout.error = e.message
|
||||
}
|
||||
val binding = binding ?: return
|
||||
val activity = activity ?: return
|
||||
val name = binding.tunnelNameText.text.toString()
|
||||
activity.lifecycleScope.launch {
|
||||
try {
|
||||
Application.getTunnelManager().create(name, config)
|
||||
dismiss()
|
||||
} catch (e: Throwable) {
|
||||
binding.tunnelNameTextLayout.error = e.message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.fragment
|
||||
@@ -17,9 +17,9 @@ import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.android.databinding.TunnelDetailFragmentBinding
|
||||
import com.wireguard.android.databinding.TunnelDetailPeerBinding
|
||||
import com.wireguard.android.model.ObservableTunnel
|
||||
import com.wireguard.android.util.QuantityFormatter
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
|
||||
/**
|
||||
* Fragment that shows details about a specific tunnel.
|
||||
@@ -27,18 +27,7 @@ import java.util.TimerTask
|
||||
class TunnelDetailFragment : BaseFragment() {
|
||||
private var binding: TunnelDetailFragmentBinding? = null
|
||||
private var lastState = Tunnel.State.TOGGLE
|
||||
private var timer: Timer? = null
|
||||
|
||||
private fun formatBytes(bytes: Long): String {
|
||||
val context = requireContext()
|
||||
return when {
|
||||
bytes < 1024 -> context.getString(R.string.transfer_bytes, bytes)
|
||||
bytes < 1024 * 1024 -> context.getString(R.string.transfer_kibibytes, bytes / 1024.0)
|
||||
bytes < 1024 * 1024 * 1024 -> context.getString(R.string.transfer_mibibytes, bytes / (1024.0 * 1024.0))
|
||||
bytes < 1024 * 1024 * 1024 * 1024L -> context.getString(R.string.transfer_gibibytes, bytes / (1024.0 * 1024.0 * 1024.0))
|
||||
else -> context.getString(R.string.transfer_tibibytes, bytes / (1024.0 * 1024.0 * 1024.0) / 1024.0)
|
||||
}
|
||||
}
|
||||
private var timerActive = true
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -54,7 +43,7 @@ class TunnelDetailFragment : BaseFragment() {
|
||||
super.onCreateView(inflater, container, savedInstanceState)
|
||||
binding = TunnelDetailFragmentBinding.inflate(inflater, container, false)
|
||||
binding?.executePendingBindings()
|
||||
return binding!!.root
|
||||
return binding?.root
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
@@ -64,34 +53,36 @@ class TunnelDetailFragment : BaseFragment() {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
timer = Timer()
|
||||
timer!!.scheduleAtFixedRate(object : TimerTask() {
|
||||
override fun run() {
|
||||
timerActive = true
|
||||
lifecycleScope.launch {
|
||||
while (timerActive) {
|
||||
updateStats()
|
||||
delay(1000)
|
||||
}
|
||||
}, 0, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?, newTunnel: ObservableTunnel?) {
|
||||
binding ?: return
|
||||
binding!!.tunnel = newTunnel
|
||||
if (newTunnel == null) binding!!.config = null else lifecycleScope.launch {
|
||||
try {
|
||||
binding!!.config = newTunnel.getConfigAsync()
|
||||
} catch (_: Throwable) {
|
||||
binding!!.config = null
|
||||
val binding = binding ?: return
|
||||
binding.tunnel = newTunnel
|
||||
if (newTunnel == null) {
|
||||
binding.config = null
|
||||
} else {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
binding.config = newTunnel.getConfigAsync()
|
||||
} catch (_: Throwable) {
|
||||
binding.config = null
|
||||
}
|
||||
}
|
||||
}
|
||||
lastState = Tunnel.State.TOGGLE
|
||||
updateStats()
|
||||
lifecycleScope.launch { updateStats() }
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
timerActive = false
|
||||
super.onStop()
|
||||
if (timer != null) {
|
||||
timer!!.cancel()
|
||||
timer = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewStateRestored(savedInstanceState: Bundle?) {
|
||||
@@ -101,37 +92,36 @@ class TunnelDetailFragment : BaseFragment() {
|
||||
super.onViewStateRestored(savedInstanceState)
|
||||
}
|
||||
|
||||
private fun updateStats() {
|
||||
if (binding == null || !isResumed) return
|
||||
val tunnel = binding!!.tunnel ?: return
|
||||
private suspend fun updateStats() {
|
||||
val binding = binding ?: return
|
||||
val tunnel = binding.tunnel ?: return
|
||||
if (!isResumed) return
|
||||
val state = tunnel.state
|
||||
if (state != Tunnel.State.UP && lastState == state) return
|
||||
lastState = state
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val statistics = tunnel.getStatisticsAsync()
|
||||
for (i in 0 until binding!!.peersLayout.childCount) {
|
||||
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) {
|
||||
peer.transferLabel.visibility = View.GONE
|
||||
peer.transferText.visibility = View.GONE
|
||||
continue
|
||||
}
|
||||
peer.transferText.text = requireContext().getString(R.string.transfer_rx_tx, formatBytes(rx), formatBytes(tx))
|
||||
peer.transferLabel.visibility = View.VISIBLE
|
||||
peer.transferText.visibility = View.VISIBLE
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
for (i in 0 until binding!!.peersLayout.childCount) {
|
||||
val peer: TunnelDetailPeerBinding = DataBindingUtil.getBinding(binding!!.peersLayout.getChildAt(i))
|
||||
?: continue
|
||||
try {
|
||||
val statistics = tunnel.getStatisticsAsync()
|
||||
for (i in 0 until binding.peersLayout.childCount) {
|
||||
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) {
|
||||
peer.transferLabel.visibility = View.GONE
|
||||
peer.transferText.visibility = View.GONE
|
||||
continue
|
||||
}
|
||||
peer.transferText.text = getString(R.string.transfer_rx_tx, QuantityFormatter.formatBytes(rx), QuantityFormatter.formatBytes(tx))
|
||||
peer.transferLabel.visibility = View.VISIBLE
|
||||
peer.transferText.visibility = View.VISIBLE
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
for (i in 0 until binding.peersLayout.childCount) {
|
||||
val peer: TunnelDetailPeerBinding = DataBindingUtil.getBinding(binding.peersLayout.getChildAt(i))
|
||||
?: continue
|
||||
peer.transferLabel.visibility = View.GONE
|
||||
peer.transferText.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.fragment
|
||||
@@ -39,24 +39,27 @@ class TunnelEditorFragment : BaseFragment() {
|
||||
private var haveShownKeys = false
|
||||
private var binding: TunnelEditorFragmentBinding? = null
|
||||
private var tunnel: ObservableTunnel? = null
|
||||
|
||||
private fun onConfigLoaded(config: Config) {
|
||||
binding?.config = ConfigProxy(config)
|
||||
}
|
||||
|
||||
private fun onConfigSaved(savedTunnel: Tunnel, throwable: Throwable?) {
|
||||
val message: String
|
||||
val ctx = activity ?: Application.get()
|
||||
if (throwable == null) {
|
||||
message = getString(R.string.config_save_success, savedTunnel.name)
|
||||
val message = ctx.getString(R.string.config_save_success, savedTunnel.name)
|
||||
Log.d(TAG, message)
|
||||
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(ctx, message, Toast.LENGTH_SHORT).show()
|
||||
onFinished()
|
||||
} else {
|
||||
val error = ErrorMessages[throwable]
|
||||
message = getString(R.string.config_save_error, savedTunnel.name, error)
|
||||
val message = ctx.getString(R.string.config_save_error, savedTunnel.name, error)
|
||||
Log.e(TAG, message, throwable)
|
||||
binding?.let {
|
||||
Snackbar.make(it.mainContainer, message, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
val binding = binding
|
||||
if (binding != null)
|
||||
Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show()
|
||||
else
|
||||
Toast.makeText(ctx, message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +118,8 @@ class TunnelEditorFragment : BaseFragment() {
|
||||
Snackbar.make(binding!!.mainContainer, error, Snackbar.LENGTH_LONG).show()
|
||||
return false
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
val activity = requireActivity()
|
||||
activity.lifecycleScope.launch {
|
||||
when {
|
||||
tunnel == null -> {
|
||||
Log.d(TAG, "Attempting to create new tunnel " + binding!!.name)
|
||||
@@ -209,46 +213,48 @@ class TunnelEditorFragment : BaseFragment() {
|
||||
}
|
||||
|
||||
private fun onTunnelCreated(newTunnel: ObservableTunnel?, throwable: Throwable?) {
|
||||
val message: String
|
||||
val ctx = activity ?: Application.get()
|
||||
if (throwable == null) {
|
||||
tunnel = newTunnel
|
||||
message = getString(R.string.tunnel_create_success, tunnel!!.name)
|
||||
val message = ctx.getString(R.string.tunnel_create_success, tunnel!!.name)
|
||||
Log.d(TAG, message)
|
||||
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(ctx, message, Toast.LENGTH_SHORT).show()
|
||||
onFinished()
|
||||
} else {
|
||||
val error = ErrorMessages[throwable]
|
||||
message = getString(R.string.tunnel_create_error, error)
|
||||
val message = ctx.getString(R.string.tunnel_create_error, error)
|
||||
Log.e(TAG, message, throwable)
|
||||
binding?.let {
|
||||
Snackbar.make(it.mainContainer, message, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
val binding = binding
|
||||
if (binding != null)
|
||||
Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show()
|
||||
else
|
||||
Toast.makeText(ctx, message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onTunnelRenamed(renamedTunnel: ObservableTunnel, newConfig: Config,
|
||||
throwable: Throwable?) {
|
||||
val message: String
|
||||
private suspend fun onTunnelRenamed(renamedTunnel: ObservableTunnel, newConfig: Config,
|
||||
throwable: Throwable?) {
|
||||
val ctx = activity ?: Application.get()
|
||||
if (throwable == null) {
|
||||
message = getString(R.string.tunnel_rename_success, renamedTunnel.name)
|
||||
val message = ctx.getString(R.string.tunnel_rename_success, renamedTunnel.name)
|
||||
Log.d(TAG, message)
|
||||
// Now save the rest of configuration changes.
|
||||
Log.d(TAG, "Attempting to save config of renamed tunnel " + tunnel!!.name)
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
renamedTunnel.setConfigAsync(newConfig)
|
||||
onConfigSaved(renamedTunnel, null)
|
||||
} catch (e: Throwable) {
|
||||
onConfigSaved(renamedTunnel, e)
|
||||
}
|
||||
try {
|
||||
renamedTunnel.setConfigAsync(newConfig)
|
||||
onConfigSaved(renamedTunnel, null)
|
||||
} catch (e: Throwable) {
|
||||
onConfigSaved(renamedTunnel, e)
|
||||
}
|
||||
} else {
|
||||
val error = ErrorMessages[throwable]
|
||||
message = getString(R.string.tunnel_rename_error, error)
|
||||
val message = ctx.getString(R.string.tunnel_rename_error, error)
|
||||
Log.e(TAG, message, throwable)
|
||||
binding?.let {
|
||||
Snackbar.make(it.mainContainer, message, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
val binding = binding
|
||||
if (binding != null)
|
||||
Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show()
|
||||
else
|
||||
Toast.makeText(ctx, message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,6 +297,7 @@ class TunnelEditorFragment : BaseFragment() {
|
||||
Snackbar.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
is BiometricAuthenticator.Result.Cancelled -> {}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.fragment
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.res.Resources
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.OpenableColumns
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
@@ -17,39 +15,32 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.Animation
|
||||
import android.view.animation.AnimationUtils
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.zxing.integration.android.IntentIntegrator
|
||||
import com.google.zxing.qrcode.QRCodeReader
|
||||
import com.journeyapps.barcodescanner.ScanContract
|
||||
import com.journeyapps.barcodescanner.ScanOptions
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.activity.TunnelCreatorActivity
|
||||
import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler
|
||||
import com.wireguard.android.databinding.TunnelListFragmentBinding
|
||||
import com.wireguard.android.databinding.TunnelListItemBinding
|
||||
import com.wireguard.android.fragment.ConfigNamingDialogFragment.Companion.newInstance
|
||||
import com.wireguard.android.model.ObservableTunnel
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import com.wireguard.android.util.QrCodeFromFileScanner
|
||||
import com.wireguard.android.util.TunnelImporter
|
||||
import com.wireguard.android.widget.MultiselectableRelativeLayout
|
||||
import com.wireguard.config.Config
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.BufferedReader
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStreamReader
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.ArrayList
|
||||
import java.util.HashSet
|
||||
import java.util.Locale
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
/**
|
||||
* Fragment containing a list of known WireGuard tunnels. It allows creating and deleting tunnels.
|
||||
@@ -59,113 +50,32 @@ class TunnelListFragment : BaseFragment() {
|
||||
private var actionMode: ActionMode? = null
|
||||
private var binding: TunnelListFragmentBinding? = null
|
||||
private val tunnelFileImportResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { data ->
|
||||
importTunnel(data)
|
||||
}
|
||||
|
||||
private val qrImportResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
val qrCode = IntentIntegrator.parseActivityResult(result.resultCode, result.data)
|
||||
qrCode?.contents?.let { importTunnel(it) }
|
||||
}
|
||||
|
||||
private fun importTunnel(configText: String) {
|
||||
try {
|
||||
// Ensure the config text is parseable before proceeding…
|
||||
Config.parse(ByteArrayInputStream(configText.toByteArray(StandardCharsets.UTF_8)))
|
||||
|
||||
// Config text is valid, now create the tunnel…
|
||||
newInstance(configText).show(parentFragmentManager, null)
|
||||
} catch (e: Throwable) {
|
||||
onTunnelImportFinished(emptyList(), listOf<Throwable>(e))
|
||||
if (data == null) return@registerForActivityResult
|
||||
val activity = activity ?: return@registerForActivityResult
|
||||
val contentResolver = activity.contentResolver ?: return@registerForActivityResult
|
||||
activity.lifecycleScope.launch {
|
||||
if (QrCodeFromFileScanner.validContentType(contentResolver, data)) {
|
||||
try {
|
||||
val qrCodeFromFileScanner = QrCodeFromFileScanner(contentResolver, QRCodeReader())
|
||||
val result = qrCodeFromFileScanner.scan(data)
|
||||
TunnelImporter.importTunnel(parentFragmentManager, result.text) { showSnackbar(it) }
|
||||
} catch (e: Exception) {
|
||||
val error = ErrorMessages[e]
|
||||
val message = requireContext().getString(R.string.import_error, error)
|
||||
Log.e(TAG, message, e)
|
||||
showSnackbar(message)
|
||||
}
|
||||
} else {
|
||||
TunnelImporter.importTunnel(contentResolver, data) { showSnackbar(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun importTunnel(uri: Uri?) {
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
val activity = activity
|
||||
if (activity == null || uri == null) {
|
||||
return@withContext
|
||||
}
|
||||
val contentResolver = activity.contentResolver
|
||||
val futureTunnels = ArrayList<Deferred<ObservableTunnel>>()
|
||||
val throwables = ArrayList<Throwable>()
|
||||
try {
|
||||
val columns = arrayOf(OpenableColumns.DISPLAY_NAME)
|
||||
var name = ""
|
||||
contentResolver.query(uri, columns, null, null, null)?.use { cursor ->
|
||||
if (cursor.moveToFirst() && !cursor.isNull(0)) {
|
||||
name = cursor.getString(0)
|
||||
}
|
||||
}
|
||||
if (name.isEmpty()) {
|
||||
name = Uri.decode(uri.lastPathSegment)
|
||||
}
|
||||
var idx = name.lastIndexOf('/')
|
||||
if (idx >= 0) {
|
||||
require(idx < name.length - 1) { resources.getString(R.string.illegal_filename_error, name) }
|
||||
name = name.substring(idx + 1)
|
||||
}
|
||||
val isZip = name.toLowerCase(Locale.ROOT).endsWith(".zip")
|
||||
if (name.toLowerCase(Locale.ROOT).endsWith(".conf")) {
|
||||
name = name.substring(0, name.length - ".conf".length)
|
||||
} else {
|
||||
require(isZip) { resources.getString(R.string.bad_extension_error) }
|
||||
}
|
||||
|
||||
if (isZip) {
|
||||
ZipInputStream(contentResolver.openInputStream(uri)).use { zip ->
|
||||
val reader = BufferedReader(InputStreamReader(zip, StandardCharsets.UTF_8))
|
||||
var entry: ZipEntry?
|
||||
while (true) {
|
||||
entry = zip.nextEntry ?: break
|
||||
name = entry.name
|
||||
idx = name.lastIndexOf('/')
|
||||
if (idx >= 0) {
|
||||
if (idx >= name.length - 1) {
|
||||
continue
|
||||
}
|
||||
name = name.substring(name.lastIndexOf('/') + 1)
|
||||
}
|
||||
if (name.toLowerCase(Locale.ROOT).endsWith(".conf")) {
|
||||
name = name.substring(0, name.length - ".conf".length)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
try {
|
||||
Config.parse(reader)
|
||||
} catch (e: Throwable) {
|
||||
throwables.add(e)
|
||||
null
|
||||
}?.let {
|
||||
val nameCopy = name
|
||||
futureTunnels.add(async(SupervisorJob()) { Application.getTunnelManager().create(nameCopy, it) })
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
futureTunnels.add(async(SupervisorJob()) { Application.getTunnelManager().create(name, Config.parse(contentResolver.openInputStream(uri)!!)) })
|
||||
}
|
||||
|
||||
if (futureTunnels.isEmpty()) {
|
||||
if (throwables.size == 1) {
|
||||
throw throwables[0]
|
||||
} else {
|
||||
require(throwables.isNotEmpty()) { resources.getString(R.string.no_configs_error) }
|
||||
}
|
||||
}
|
||||
val tunnels = futureTunnels.mapNotNull {
|
||||
try {
|
||||
it.await()
|
||||
} catch (e: Throwable) {
|
||||
throwables.add(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main.immediate) { onTunnelImportFinished(tunnels, throwables) }
|
||||
} catch (e: Throwable) {
|
||||
withContext(Dispatchers.Main.immediate) { onTunnelImportFinished(emptyList(), listOf(e)) }
|
||||
}
|
||||
}
|
||||
private val qrImportResultLauncher = registerForActivityResult(ScanContract()) { result ->
|
||||
val qrCode = result.contents
|
||||
val activity = activity
|
||||
if (qrCode != null && activity != null) {
|
||||
activity.lifecycleScope.launch { TunnelImporter.importTunnel(parentFragmentManager, qrCode) { showSnackbar(it) } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,11 +105,10 @@ class TunnelListFragment : BaseFragment() {
|
||||
tunnelFileImportResultLauncher.launch("*/*")
|
||||
}
|
||||
AddTunnelsSheet.REQUEST_SCAN -> {
|
||||
qrImportResultLauncher.launch(IntentIntegrator(requireActivity())
|
||||
qrImportResultLauncher.launch(ScanOptions()
|
||||
.setOrientationLocked(false)
|
||||
.setBeepEnabled(false)
|
||||
.setPrompt(getString(R.string.qr_code_hint))
|
||||
.createScanIntent())
|
||||
.setPrompt(getString(R.string.qr_code_hint)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,7 +116,7 @@ class TunnelListFragment : BaseFragment() {
|
||||
}
|
||||
executePendingBindings()
|
||||
}
|
||||
return binding!!.root
|
||||
return binding?.root
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
@@ -224,43 +133,24 @@ class TunnelListFragment : BaseFragment() {
|
||||
binding ?: return
|
||||
lifecycleScope.launch {
|
||||
val tunnels = Application.getTunnelManager().getTunnels()
|
||||
if (newTunnel != null) viewForTunnel(newTunnel, tunnels).setSingleSelected(true)
|
||||
if (oldTunnel != null) viewForTunnel(oldTunnel, tunnels).setSingleSelected(false)
|
||||
if (newTunnel != null) viewForTunnel(newTunnel, tunnels)?.setSingleSelected(true)
|
||||
if (oldTunnel != null) viewForTunnel(oldTunnel, tunnels)?.setSingleSelected(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onTunnelDeletionFinished(count: Int, throwable: Throwable?) {
|
||||
val message: String
|
||||
val ctx = activity ?: Application.get()
|
||||
if (throwable == null) {
|
||||
message = resources.getQuantityString(R.plurals.delete_success, count, count)
|
||||
message = ctx.resources.getQuantityString(R.plurals.delete_success, count, count)
|
||||
} else {
|
||||
val error = ErrorMessages[throwable]
|
||||
message = resources.getQuantityString(R.plurals.delete_error, count, count, error)
|
||||
message = ctx.resources.getQuantityString(R.plurals.delete_error, count, count, error)
|
||||
Log.e(TAG, message, throwable)
|
||||
}
|
||||
showSnackbar(message)
|
||||
}
|
||||
|
||||
private fun onTunnelImportFinished(tunnels: List<ObservableTunnel>, throwables: Collection<Throwable>) {
|
||||
var message = ""
|
||||
for (throwable in throwables) {
|
||||
val error = ErrorMessages[throwable]
|
||||
message = getString(R.string.import_error, error)
|
||||
Log.e(TAG, message, throwable)
|
||||
}
|
||||
if (tunnels.size == 1 && throwables.isEmpty())
|
||||
message = getString(R.string.import_success, tunnels[0].name)
|
||||
else if (tunnels.isEmpty() && throwables.size == 1)
|
||||
else if (throwables.isEmpty())
|
||||
message = resources.getQuantityString(R.plurals.import_total_success,
|
||||
tunnels.size, tunnels.size)
|
||||
else if (!throwables.isEmpty())
|
||||
message = resources.getQuantityString(R.plurals.import_partial_success,
|
||||
tunnels.size + throwables.size,
|
||||
tunnels.size, tunnels.size + throwables.size)
|
||||
showSnackbar(message)
|
||||
}
|
||||
|
||||
override fun onViewStateRestored(savedInstanceState: Bundle?) {
|
||||
super.onViewStateRestored(savedInstanceState)
|
||||
binding ?: return
|
||||
@@ -289,15 +179,17 @@ class TunnelListFragment : BaseFragment() {
|
||||
}
|
||||
|
||||
private fun showSnackbar(message: CharSequence) {
|
||||
binding?.let {
|
||||
Snackbar.make(it.mainContainer, message, Snackbar.LENGTH_LONG)
|
||||
.setAnchorView(it.createFab)
|
||||
val binding = binding
|
||||
if (binding != null)
|
||||
Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG)
|
||||
.setAnchorView(binding.createFab)
|
||||
.show()
|
||||
}
|
||||
else
|
||||
Toast.makeText(activity ?: Application.get(), message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
private fun viewForTunnel(tunnel: ObservableTunnel, tunnels: List<*>): MultiselectableRelativeLayout {
|
||||
return binding!!.tunnelList.findViewHolderForAdapterPosition(tunnels.indexOf(tunnel))!!.itemView as MultiselectableRelativeLayout
|
||||
private fun viewForTunnel(tunnel: ObservableTunnel, tunnels: List<*>): MultiselectableRelativeLayout? {
|
||||
return binding?.tunnelList?.findViewHolderForAdapterPosition(tunnels.indexOf(tunnel))?.itemView as? MultiselectableRelativeLayout
|
||||
}
|
||||
|
||||
private inner class ActionModeListener : ActionMode.Callback {
|
||||
@@ -311,13 +203,14 @@ class TunnelListFragment : BaseFragment() {
|
||||
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.menu_action_delete -> {
|
||||
val activity = activity ?: return true
|
||||
val copyCheckedItems = HashSet(checkedItems)
|
||||
binding?.createFab?.apply {
|
||||
visibility = View.VISIBLE
|
||||
scaleX = 1f
|
||||
scaleY = 1f
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
activity.lifecycleScope.launch {
|
||||
try {
|
||||
val tunnels = Application.getTunnelManager().getTunnels()
|
||||
val tunnelsToDelete = ArrayList<ObservableTunnel>()
|
||||
@@ -361,7 +254,7 @@ class TunnelListFragment : BaseFragment() {
|
||||
resources = null
|
||||
animateFab(binding?.createFab, true)
|
||||
checkedItems.clear()
|
||||
binding!!.tunnelList.adapter!!.notifyDataSetChanged()
|
||||
binding?.tunnelList?.adapter?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
@@ -423,8 +316,6 @@ class TunnelListFragment : BaseFragment() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val REQUEST_IMPORT = 1
|
||||
private const val REQUEST_TARGET_FRAGMENT = 2
|
||||
private const val CHECKED_ITEMS = "CHECKED_ITEMS"
|
||||
private const val TAG = "WireGuard/TunnelListFragment"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.model
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.model
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.model
|
||||
|
||||
import java.util.Locale
|
||||
|
||||
object TunnelComparator : Comparator<String> {
|
||||
private class NaturalSortString(originalString: String) {
|
||||
class NaturalSortToken(val maybeString: String?, val maybeNumber: Int?) : Comparable<NaturalSortToken> {
|
||||
@@ -29,7 +27,7 @@ object TunnelComparator : Comparator<String> {
|
||||
val tokens: MutableList<NaturalSortToken> = ArrayList()
|
||||
|
||||
init {
|
||||
for (s in NATURAL_SORT_DIGIT_FINDER.findAll(originalString.split(WHITESPACE_FINDER).joinToString(" ").toLowerCase(Locale.ENGLISH))) {
|
||||
for (s in NATURAL_SORT_DIGIT_FINDER.findAll(originalString.split(WHITESPACE_FINDER).joinToString(" ").lowercase())) {
|
||||
try {
|
||||
val n = s.value.toInt()
|
||||
tokens.add(NaturalSortToken(null, n))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.model
|
||||
@@ -9,6 +9,7 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.databinding.BaseObservable
|
||||
import androidx.databinding.Bindable
|
||||
import com.wireguard.android.Application.Companion.get
|
||||
@@ -20,6 +21,7 @@ import com.wireguard.android.backend.Statistics
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.android.configStore.ConfigStore
|
||||
import com.wireguard.android.databinding.ObservableSortedKeyedArrayList
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import com.wireguard.android.util.UserKnobs
|
||||
import com.wireguard.android.util.applicationScope
|
||||
import com.wireguard.config.Config
|
||||
@@ -229,7 +231,11 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
|
||||
val tunnelName = intent.getStringExtra("tunnel") ?: return@launch
|
||||
val tunnels = manager.getTunnels()
|
||||
val tunnel = tunnels[tunnelName] ?: return@launch
|
||||
manager.setTunnelState(tunnel, state)
|
||||
try {
|
||||
manager.setTunnelState(tunnel, state)
|
||||
} catch (e: Throwable) {
|
||||
Toast.makeText(context, ErrorMessages[e], Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-10
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.preference
|
||||
@@ -8,6 +8,7 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.Preference
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
@@ -15,6 +16,7 @@ import com.wireguard.android.activity.SettingsActivity
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.android.backend.WgQuickBackend
|
||||
import com.wireguard.android.util.UserKnobs
|
||||
import com.wireguard.android.util.activity
|
||||
import com.wireguard.android.util.lifecycleScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
@@ -24,7 +26,7 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class KernelModuleDisablerPreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
|
||||
class KernelModuleEnablerPreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
|
||||
private var state = State.UNKNOWN
|
||||
|
||||
init {
|
||||
@@ -39,13 +41,13 @@ class KernelModuleDisablerPreference(context: Context, attrs: AttributeSet?) : P
|
||||
override fun getTitle() = if (state == State.UNKNOWN) "" else context.getString(state.titleResourceId)
|
||||
|
||||
override fun onClick() {
|
||||
lifecycleScope.launch {
|
||||
activity.lifecycleScope.launch {
|
||||
if (state == State.DISABLED) {
|
||||
setState(State.ENABLING)
|
||||
UserKnobs.setDisableKernelModule(false)
|
||||
UserKnobs.setEnableKernelModule(true)
|
||||
} else if (state == State.ENABLED) {
|
||||
setState(State.DISABLING)
|
||||
UserKnobs.setDisableKernelModule(true)
|
||||
UserKnobs.setEnableKernelModule(false)
|
||||
}
|
||||
val observableTunnels = Application.getTunnelManager().getTunnels()
|
||||
val downings = observableTunnels.map { async(SupervisorJob()) { it.setStateAsync(Tunnel.State.DOWN) } }
|
||||
@@ -74,13 +76,13 @@ class KernelModuleDisablerPreference(context: Context, attrs: AttributeSet?) : P
|
||||
|
||||
private enum class State(val titleResourceId: Int, val summaryResourceId: Int, val shouldEnableView: Boolean, val visible: Boolean) {
|
||||
UNKNOWN(0, 0, false, false),
|
||||
ENABLED(R.string.module_disabler_enabled_title, R.string.module_disabler_enabled_summary, true, true),
|
||||
DISABLED(R.string.module_disabler_disabled_title, R.string.module_disabler_disabled_summary, true, true),
|
||||
ENABLING(R.string.module_disabler_disabled_title, R.string.success_application_will_restart, false, true),
|
||||
DISABLING(R.string.module_disabler_enabled_title, R.string.success_application_will_restart, false, true);
|
||||
ENABLED(R.string.module_enabler_enabled_title, R.string.module_enabler_enabled_summary, true, true),
|
||||
DISABLED(R.string.module_enabler_disabled_title, R.string.module_enabler_disabled_summary, true, true),
|
||||
ENABLING(R.string.module_enabler_disabled_title, R.string.success_application_will_restart, false, true),
|
||||
DISABLING(R.string.module_enabler_enabled_title, R.string.success_application_will_restart, false, true);
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "WireGuard/KernelModuleDisablerPreference"
|
||||
private const val TAG = "WireGuard/KernelModuleEnablerPreference"
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2019 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.preference
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.system.OsConstants
|
||||
import android.util.AttributeSet
|
||||
import android.widget.Toast
|
||||
import androidx.preference.Preference
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.activity.SettingsActivity
|
||||
import com.wireguard.android.util.ErrorMessages
|
||||
import com.wireguard.android.util.UserKnobs
|
||||
import com.wireguard.android.util.lifecycleScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class ModuleDownloaderPreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
|
||||
private var state = State.INITIAL
|
||||
override fun getSummary() = context.getString(state.messageResourceId)
|
||||
|
||||
override fun getTitle() = context.getString(R.string.module_installer_title)
|
||||
|
||||
override fun onClick() {
|
||||
setState(State.WORKING)
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
when (withContext(Dispatchers.IO) { Application.getModuleLoader().download() }) {
|
||||
OsConstants.ENOENT -> setState(State.NOTFOUND)
|
||||
OsConstants.EXIT_SUCCESS -> {
|
||||
setState(State.SUCCESS)
|
||||
UserKnobs.setDisableKernelModule(null)
|
||||
withContext(Dispatchers.IO) {
|
||||
val restartIntent = Intent(context, SettingsActivity::class.java)
|
||||
restartIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
restartIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
Application.get().startActivity(restartIntent)
|
||||
exitProcess(0)
|
||||
}
|
||||
}
|
||||
else -> setState(State.FAILURE)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
setState(State.FAILURE)
|
||||
Toast.makeText(context, ErrorMessages[e], Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setState(state: State) {
|
||||
if (this.state == state) return
|
||||
this.state = state
|
||||
if (isEnabled != state.shouldEnableView) isEnabled = state.shouldEnableView
|
||||
notifyChanged()
|
||||
}
|
||||
|
||||
private enum class State(val messageResourceId: Int, val shouldEnableView: Boolean) {
|
||||
INITIAL(R.string.module_installer_initial, true),
|
||||
FAILURE(R.string.module_installer_error, true),
|
||||
WORKING(R.string.module_installer_working, false),
|
||||
SUCCESS(R.string.success_application_will_restart, false),
|
||||
NOTFOUND(R.string.module_installer_not_found, false);
|
||||
}
|
||||
}
|
||||
+22
-19
@@ -1,16 +1,19 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.preference
|
||||
|
||||
import androidx.datastore.DataStore
|
||||
import androidx.datastore.preferences.Preferences
|
||||
import androidx.datastore.preferences.edit
|
||||
import androidx.datastore.preferences.preferencesKey
|
||||
import androidx.datastore.preferences.preferencesSetKey
|
||||
import androidx.datastore.preferences.remove
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.floatPreferencesKey
|
||||
import androidx.datastore.preferences.core.intPreferencesKey
|
||||
import androidx.datastore.preferences.core.longPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringSetPreferencesKey
|
||||
import androidx.preference.PreferenceDataStore
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.first
|
||||
@@ -21,7 +24,7 @@ import kotlinx.coroutines.runBlocking
|
||||
class PreferencesPreferenceDataStore(private val coroutineScope: CoroutineScope, private val dataStore: DataStore<Preferences>) : PreferenceDataStore() {
|
||||
override fun putString(key: String?, value: String?) {
|
||||
if (key == null) return
|
||||
val pk = preferencesKey<String>(key)
|
||||
val pk = stringPreferencesKey(key)
|
||||
coroutineScope.launch {
|
||||
dataStore.edit {
|
||||
if (value == null) it.remove(pk)
|
||||
@@ -32,7 +35,7 @@ class PreferencesPreferenceDataStore(private val coroutineScope: CoroutineScope,
|
||||
|
||||
override fun putStringSet(key: String?, values: Set<String?>?) {
|
||||
if (key == null) return
|
||||
val pk = preferencesSetKey<String>(key)
|
||||
val pk = stringSetPreferencesKey(key)
|
||||
val filteredValues = values?.filterNotNull()?.toSet()
|
||||
coroutineScope.launch {
|
||||
dataStore.edit {
|
||||
@@ -44,7 +47,7 @@ class PreferencesPreferenceDataStore(private val coroutineScope: CoroutineScope,
|
||||
|
||||
override fun putInt(key: String?, value: Int) {
|
||||
if (key == null) return
|
||||
val pk = preferencesKey<Int>(key)
|
||||
val pk = intPreferencesKey(key)
|
||||
coroutineScope.launch {
|
||||
dataStore.edit {
|
||||
it[pk] = value
|
||||
@@ -54,7 +57,7 @@ class PreferencesPreferenceDataStore(private val coroutineScope: CoroutineScope,
|
||||
|
||||
override fun putLong(key: String?, value: Long) {
|
||||
if (key == null) return
|
||||
val pk = preferencesKey<Long>(key)
|
||||
val pk = longPreferencesKey(key)
|
||||
coroutineScope.launch {
|
||||
dataStore.edit {
|
||||
it[pk] = value
|
||||
@@ -64,7 +67,7 @@ class PreferencesPreferenceDataStore(private val coroutineScope: CoroutineScope,
|
||||
|
||||
override fun putFloat(key: String?, value: Float) {
|
||||
if (key == null) return
|
||||
val pk = preferencesKey<Float>(key)
|
||||
val pk = floatPreferencesKey(key)
|
||||
coroutineScope.launch {
|
||||
dataStore.edit {
|
||||
it[pk] = value
|
||||
@@ -74,7 +77,7 @@ class PreferencesPreferenceDataStore(private val coroutineScope: CoroutineScope,
|
||||
|
||||
override fun putBoolean(key: String?, value: Boolean) {
|
||||
if (key == null) return
|
||||
val pk = preferencesKey<Boolean>(key)
|
||||
val pk = booleanPreferencesKey(key)
|
||||
coroutineScope.launch {
|
||||
dataStore.edit {
|
||||
it[pk] = value
|
||||
@@ -84,7 +87,7 @@ class PreferencesPreferenceDataStore(private val coroutineScope: CoroutineScope,
|
||||
|
||||
override fun getString(key: String?, defValue: String?): String? {
|
||||
if (key == null) return defValue
|
||||
val pk = preferencesKey<String>(key)
|
||||
val pk = stringPreferencesKey(key)
|
||||
return runBlocking {
|
||||
dataStore.data.map { it[pk] ?: defValue }.first()
|
||||
}
|
||||
@@ -92,7 +95,7 @@ class PreferencesPreferenceDataStore(private val coroutineScope: CoroutineScope,
|
||||
|
||||
override fun getStringSet(key: String?, defValues: Set<String?>?): Set<String?>? {
|
||||
if (key == null) return defValues
|
||||
val pk = preferencesSetKey<String>(key)
|
||||
val pk = stringSetPreferencesKey(key)
|
||||
return runBlocking {
|
||||
dataStore.data.map { it[pk] ?: defValues }.first()
|
||||
}
|
||||
@@ -100,7 +103,7 @@ class PreferencesPreferenceDataStore(private val coroutineScope: CoroutineScope,
|
||||
|
||||
override fun getInt(key: String?, defValue: Int): Int {
|
||||
if (key == null) return defValue
|
||||
val pk = preferencesKey<Int>(key)
|
||||
val pk = intPreferencesKey(key)
|
||||
return runBlocking {
|
||||
dataStore.data.map { it[pk] ?: defValue }.first()
|
||||
}
|
||||
@@ -108,7 +111,7 @@ class PreferencesPreferenceDataStore(private val coroutineScope: CoroutineScope,
|
||||
|
||||
override fun getLong(key: String?, defValue: Long): Long {
|
||||
if (key == null) return defValue
|
||||
val pk = preferencesKey<Long>(key)
|
||||
val pk = longPreferencesKey(key)
|
||||
return runBlocking {
|
||||
dataStore.data.map { it[pk] ?: defValue }.first()
|
||||
}
|
||||
@@ -116,7 +119,7 @@ class PreferencesPreferenceDataStore(private val coroutineScope: CoroutineScope,
|
||||
|
||||
override fun getFloat(key: String?, defValue: Float): Float {
|
||||
if (key == null) return defValue
|
||||
val pk = preferencesKey<Float>(key)
|
||||
val pk = floatPreferencesKey(key)
|
||||
return runBlocking {
|
||||
dataStore.data.map { it[pk] ?: defValue }.first()
|
||||
}
|
||||
@@ -124,7 +127,7 @@ class PreferencesPreferenceDataStore(private val coroutineScope: CoroutineScope,
|
||||
|
||||
override fun getBoolean(key: String?, defValue: Boolean): Boolean {
|
||||
if (key == null) return defValue
|
||||
val pk = preferencesKey<Boolean>(key)
|
||||
val pk = booleanPreferencesKey(key)
|
||||
return runBlocking {
|
||||
dataStore.data.map { it[pk] ?: defValue }.first()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.preference
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.preference
|
||||
@@ -20,7 +20,6 @@ import com.wireguard.android.util.lifecycleScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.Locale
|
||||
|
||||
class VersionPreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
|
||||
private var versionSummary: String? = null
|
||||
@@ -49,12 +48,12 @@ class VersionPreference(context: Context, attrs: AttributeSet?) : Preference(con
|
||||
init {
|
||||
lifecycleScope.launch {
|
||||
val backend = Application.getBackend()
|
||||
versionSummary = getContext().getString(R.string.version_summary_checking, getBackendPrettyName(context, backend).toLowerCase(Locale.ENGLISH))
|
||||
versionSummary = getContext().getString(R.string.version_summary_checking, getBackendPrettyName(context, backend).lowercase())
|
||||
notifyChanged()
|
||||
versionSummary = try {
|
||||
getContext().getString(R.string.version_summary, getBackendPrettyName(context, backend), withContext(Dispatchers.IO) { backend.version })
|
||||
} catch (_: Throwable) {
|
||||
getContext().getString(R.string.version_summary_unknown, getBackendPrettyName(context, backend).toLowerCase(Locale.ENGLISH))
|
||||
getContext().getString(R.string.version_summary_unknown, getBackendPrettyName(context, backend).lowercase())
|
||||
}
|
||||
notifyChanged()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.preference
|
||||
@@ -32,6 +32,8 @@ import java.util.zip.ZipOutputStream
|
||||
*/
|
||||
class ZipExporterPreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
|
||||
private var exportedFilePath: String? = null
|
||||
private val downloadsFileSaver = DownloadsFileSaver(activity)
|
||||
|
||||
private fun exportZip() {
|
||||
lifecycleScope.launch {
|
||||
val tunnels = Application.getTunnelManager().getTunnels()
|
||||
@@ -41,7 +43,7 @@ class ZipExporterPreference(context: Context, attrs: AttributeSet?) : Preference
|
||||
if (configs.isEmpty()) {
|
||||
throw IllegalArgumentException(context.getString(R.string.no_tunnels_error))
|
||||
}
|
||||
val outputFile = DownloadsFileSaver.save(activity, "wireguard-export.zip", "application/zip", true)
|
||||
val outputFile = downloadsFileSaver.save("wireguard-export.zip", "application/zip", true)
|
||||
if (outputFile == null) {
|
||||
withContext(Dispatchers.Main.immediate) {
|
||||
isEnabled = true
|
||||
@@ -96,6 +98,7 @@ class ZipExporterPreference(context: Context, attrs: AttributeSet?) : Preference
|
||||
Snackbar.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
is BiometricAuthenticator.Result.Cancelled -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
@@ -9,7 +9,6 @@ import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.biometric.BiometricConstants
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricManager.Authenticators
|
||||
import androidx.biometric.BiometricPrompt
|
||||
@@ -41,12 +40,12 @@ object BiometricAuthenticator {
|
||||
super.onAuthenticationError(errorCode, errString)
|
||||
Log.d(TAG, "BiometricAuthentication error: errorCode=$errorCode, msg=$errString")
|
||||
callback(when (errorCode) {
|
||||
BiometricConstants.ERROR_CANCELED, BiometricConstants.ERROR_USER_CANCELED,
|
||||
BiometricConstants.ERROR_NEGATIVE_BUTTON -> {
|
||||
BiometricPrompt.ERROR_CANCELED, BiometricPrompt.ERROR_USER_CANCELED,
|
||||
BiometricPrompt.ERROR_NEGATIVE_BUTTON -> {
|
||||
Result.Cancelled
|
||||
}
|
||||
BiometricConstants.ERROR_HW_NOT_PRESENT, BiometricConstants.ERROR_HW_UNAVAILABLE,
|
||||
BiometricConstants.ERROR_NO_BIOMETRICS, BiometricConstants.ERROR_NO_DEVICE_CREDENTIAL -> {
|
||||
BiometricPrompt.ERROR_HW_NOT_PRESENT, BiometricPrompt.ERROR_HW_UNAVAILABLE,
|
||||
BiometricPrompt.ERROR_NO_BIOMETRICS, BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> {
|
||||
Result.HardwareUnavailableOrDisabled
|
||||
}
|
||||
else -> Result.Failure(errorCode, fragment.getString(R.string.biometric_auth_error_reason, errString))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.util
|
||||
@@ -11,6 +11,7 @@ import android.widget.TextView
|
||||
import androidx.core.content.getSystemService
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import com.wireguard.android.R
|
||||
|
||||
/**
|
||||
* Standalone utilities for interacting with the system clipboard.
|
||||
@@ -28,6 +29,6 @@ object ClipboardUtils {
|
||||
}
|
||||
val service = view.context.getSystemService<ClipboardManager>() ?: return
|
||||
service.setPrimaryClip(ClipData.newPlainText(data.second, data.first))
|
||||
Snackbar.make(view, "${data.second} copied to clipboard", Snackbar.LENGTH_LONG).show()
|
||||
Snackbar.make(view, view.context.getString(R.string.copied_to_clipboard, data.second), Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.util
|
||||
@@ -14,6 +14,7 @@ import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import android.provider.MediaStore.MediaColumns
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.wireguard.android.R
|
||||
@@ -25,9 +26,18 @@ import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
|
||||
object DownloadsFileSaver {
|
||||
@Throws(Exception::class)
|
||||
suspend fun save(context: ComponentActivity, name: String, mimeType: String?, overwriteExisting: Boolean) = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
class DownloadsFileSaver(private val context: ComponentActivity) {
|
||||
private lateinit var activityResult: ActivityResultLauncher<String>
|
||||
private lateinit var futureGrant: CompletableDeferred<Boolean>
|
||||
|
||||
init {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
futureGrant = CompletableDeferred()
|
||||
activityResult = context.registerForActivityResult(ActivityResultContracts.RequestPermission()) { ret -> futureGrant.complete(ret) }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun save(name: String, mimeType: String?, overwriteExisting: Boolean) = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val contentResolver = context.contentResolver
|
||||
if (overwriteExisting)
|
||||
@@ -66,13 +76,12 @@ object DownloadsFileSaver {
|
||||
} else {
|
||||
withContext(Dispatchers.Main.immediate) {
|
||||
if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||
val futureGrant = CompletableDeferred<Boolean>()
|
||||
val activityResult = context.registerForActivityResult(ActivityResultContracts.RequestPermission(), futureGrant::complete)
|
||||
activityResult.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
val granted = futureGrant.await()
|
||||
activityResult.unregister()
|
||||
if (!granted)
|
||||
if (!granted) {
|
||||
futureGrant = CompletableDeferred()
|
||||
return@withContext null
|
||||
}
|
||||
}
|
||||
@Suppress("DEPRECATION") val path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||
withContext(Dispatchers.IO) {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
/*
|
||||
* Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package com.wireguard.android.util
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.os.RemoteException
|
||||
import com.wireguard.android.Application.Companion.get
|
||||
import com.google.zxing.ChecksumException
|
||||
import com.google.zxing.NotFoundException
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.backend.BackendException
|
||||
import com.wireguard.android.util.RootShell.RootShellException
|
||||
@@ -36,7 +38,8 @@ object ErrorMessages {
|
||||
BackendException.Reason.VPN_NOT_AUTHORIZED to R.string.vpn_not_authorized_error,
|
||||
BackendException.Reason.UNABLE_TO_START_VPN to R.string.vpn_start_error,
|
||||
BackendException.Reason.TUN_CREATION_ERROR to R.string.tun_create_error,
|
||||
BackendException.Reason.GO_ACTIVATION_ERROR_CODE to R.string.tunnel_on_error
|
||||
BackendException.Reason.GO_ACTIVATION_ERROR_CODE to R.string.tunnel_on_error,
|
||||
BackendException.Reason.DNS_RESOLUTION_FAILURE to R.string.tunnel_dns_failure
|
||||
)
|
||||
private val KFE_FORMAT_MAP = mapOf(
|
||||
Key.Format.BASE64 to R.string.key_length_explanation_base64,
|
||||
@@ -63,7 +66,7 @@ object ErrorMessages {
|
||||
)
|
||||
|
||||
operator fun get(throwable: Throwable?): String {
|
||||
val resources = get().resources
|
||||
val resources = Application.get().resources
|
||||
if (throwable == null) return resources.getString(R.string.unknown_error)
|
||||
val rootCause = rootCause(throwable)
|
||||
return when {
|
||||
@@ -83,6 +86,12 @@ object ErrorMessages {
|
||||
rootCause is RootShellException -> {
|
||||
resources.getString(RSE_REASON_MAP.getValue(rootCause.reason), *rootCause.format)
|
||||
}
|
||||
rootCause is NotFoundException -> {
|
||||
resources.getString(R.string.error_no_qr_found)
|
||||
}
|
||||
rootCause is ChecksumException -> {
|
||||
resources.getString(R.string.error_qr_checksum)
|
||||
}
|
||||
rootCause.message != null -> {
|
||||
rootCause.message!!
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright © 2017-2022 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import com.google.zxing.BinaryBitmap
|
||||
import com.google.zxing.DecodeHintType
|
||||
import com.google.zxing.NotFoundException
|
||||
import com.google.zxing.RGBLuminanceSource
|
||||
import com.google.zxing.Reader
|
||||
import com.google.zxing.Result
|
||||
import com.google.zxing.common.HybridBinarizer
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Encapsulates the logic of scanning a barcode from a file,
|
||||
* @property contentResolver - Resolver to read the incoming data
|
||||
* @property reader - An instance of zxing's [Reader] class to parse the image
|
||||
*/
|
||||
class QrCodeFromFileScanner(
|
||||
private val contentResolver: ContentResolver,
|
||||
private val reader: Reader,
|
||||
) {
|
||||
|
||||
private fun scanBitmapForResult(source: Bitmap): Result {
|
||||
val width = source.width
|
||||
val height = source.height
|
||||
val pixels = IntArray(width * height)
|
||||
source.getPixels(pixels, 0, width, 0, 0, width, height)
|
||||
|
||||
val bBitmap = BinaryBitmap(HybridBinarizer(RGBLuminanceSource(width, height, pixels)))
|
||||
return reader.decode(bBitmap, mapOf(DecodeHintType.TRY_HARDER to true))
|
||||
}
|
||||
|
||||
private fun downscaleBitmap(source: Bitmap, scaledSize: Int): Bitmap {
|
||||
|
||||
val originalWidth = source.width
|
||||
val originalHeight = source.height
|
||||
|
||||
var newWidth = -1
|
||||
var newHeight = -1
|
||||
val multFactor: Float
|
||||
|
||||
when {
|
||||
originalHeight > originalWidth -> {
|
||||
newHeight = scaledSize
|
||||
multFactor = originalWidth.toFloat() / originalHeight.toFloat()
|
||||
newWidth = (newHeight * multFactor).toInt()
|
||||
}
|
||||
originalWidth > originalHeight -> {
|
||||
newWidth = scaledSize
|
||||
multFactor = originalHeight.toFloat() / originalWidth.toFloat()
|
||||
newHeight = (newWidth * multFactor).toInt()
|
||||
}
|
||||
originalHeight == originalWidth -> {
|
||||
newHeight = scaledSize
|
||||
newWidth = scaledSize
|
||||
}
|
||||
}
|
||||
return Bitmap.createScaledBitmap(source, newWidth, newHeight, false)
|
||||
}
|
||||
|
||||
private fun doScan(data: Uri): Result {
|
||||
Log.d(TAG, "Starting to scan an image: $data")
|
||||
contentResolver.openInputStream(data).use { inputStream ->
|
||||
val originalBitmap = BitmapFactory.decodeStream(inputStream)
|
||||
?: throw IllegalArgumentException("Can't decode stream to Bitmap")
|
||||
|
||||
return try {
|
||||
scanBitmapForResult(originalBitmap).also {
|
||||
Log.d(TAG, "Found result in original image")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Original image scan finished with error: $e, will try downscaled image")
|
||||
val scaleBitmap = downscaleBitmap(originalBitmap, 500)
|
||||
scanBitmapForResult(originalBitmap).also { scaleBitmap.recycle() }
|
||||
} finally {
|
||||
originalBitmap.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to parse incoming data
|
||||
* @return result of the decoding operation
|
||||
* @throws NotFoundException when parser didn't find QR code in the image
|
||||
*/
|
||||
suspend fun scan(data: Uri) = withContext(Dispatchers.Default) { doScan(data) }
|
||||
|
||||
companion object {
|
||||
private const val TAG = "QrCodeFromFileScanner"
|
||||
|
||||
/**
|
||||
* Given a reference to a file, check if this file could be parsed by this class
|
||||
* @return true if the file can be parsed, false if not
|
||||
*/
|
||||
fun validContentType(contentResolver: ContentResolver, data: Uri): Boolean {
|
||||
return contentResolver.getType(data)?.startsWith("image/") == true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util
|
||||
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
|
||||
object QuantityFormatter {
|
||||
fun formatBytes(bytes: Long): String {
|
||||
val context = Application.get().applicationContext
|
||||
return when {
|
||||
bytes < 1024 -> context.getString(R.string.transfer_bytes, bytes)
|
||||
bytes < 1024 * 1024 -> context.getString(R.string.transfer_kibibytes, bytes / 1024.0)
|
||||
bytes < 1024 * 1024 * 1024 -> context.getString(R.string.transfer_mibibytes, bytes / (1024.0 * 1024.0))
|
||||
bytes < 1024 * 1024 * 1024 * 1024L -> context.getString(R.string.transfer_gibibytes, bytes / (1024.0 * 1024.0 * 1024.0))
|
||||
else -> context.getString(R.string.transfer_tibibytes, bytes / (1024.0 * 1024.0 * 1024.0) / 1024.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.net.Uri
|
||||
import android.provider.OpenableColumns
|
||||
import android.util.Log
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import com.wireguard.android.Application
|
||||
import com.wireguard.android.R
|
||||
import com.wireguard.android.fragment.ConfigNamingDialogFragment
|
||||
import com.wireguard.android.model.ObservableTunnel
|
||||
import com.wireguard.config.Config
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.BufferedReader
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStreamReader
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.ArrayList
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
object TunnelImporter {
|
||||
suspend fun importTunnel(contentResolver: ContentResolver, uri: Uri, messageCallback: (CharSequence) -> Unit) = withContext(Dispatchers.IO) {
|
||||
val context = Application.get().applicationContext
|
||||
val futureTunnels = ArrayList<Deferred<ObservableTunnel>>()
|
||||
val throwables = ArrayList<Throwable>()
|
||||
try {
|
||||
val columns = arrayOf(OpenableColumns.DISPLAY_NAME)
|
||||
var name = ""
|
||||
contentResolver.query(uri, columns, null, null, null)?.use { cursor ->
|
||||
if (cursor.moveToFirst() && !cursor.isNull(0)) {
|
||||
name = cursor.getString(0)
|
||||
}
|
||||
}
|
||||
if (name.isEmpty()) {
|
||||
name = Uri.decode(uri.lastPathSegment)
|
||||
}
|
||||
var idx = name.lastIndexOf('/')
|
||||
if (idx >= 0) {
|
||||
require(idx < name.length - 1) { context.getString(R.string.illegal_filename_error, name) }
|
||||
name = name.substring(idx + 1)
|
||||
}
|
||||
val isZip = name.lowercase().endsWith(".zip")
|
||||
if (name.lowercase().endsWith(".conf")) {
|
||||
name = name.substring(0, name.length - ".conf".length)
|
||||
} else {
|
||||
require(isZip) { context.getString(R.string.bad_extension_error) }
|
||||
}
|
||||
|
||||
if (isZip) {
|
||||
ZipInputStream(contentResolver.openInputStream(uri)).use { zip ->
|
||||
val reader = BufferedReader(InputStreamReader(zip, StandardCharsets.UTF_8))
|
||||
var entry: ZipEntry?
|
||||
while (true) {
|
||||
entry = zip.nextEntry ?: break
|
||||
name = entry.name
|
||||
idx = name.lastIndexOf('/')
|
||||
if (idx >= 0) {
|
||||
if (idx >= name.length - 1) {
|
||||
continue
|
||||
}
|
||||
name = name.substring(name.lastIndexOf('/') + 1)
|
||||
}
|
||||
if (name.lowercase().endsWith(".conf")) {
|
||||
name = name.substring(0, name.length - ".conf".length)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
try {
|
||||
Config.parse(reader)
|
||||
} catch (e: Throwable) {
|
||||
throwables.add(e)
|
||||
null
|
||||
}?.let {
|
||||
val nameCopy = name
|
||||
futureTunnels.add(async(SupervisorJob()) { Application.getTunnelManager().create(nameCopy, it) })
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
futureTunnels.add(async(SupervisorJob()) { Application.getTunnelManager().create(name, Config.parse(contentResolver.openInputStream(uri)!!)) })
|
||||
}
|
||||
|
||||
if (futureTunnels.isEmpty()) {
|
||||
if (throwables.size == 1) {
|
||||
throw throwables[0]
|
||||
} else {
|
||||
require(throwables.isNotEmpty()) { context.getString(R.string.no_configs_error) }
|
||||
}
|
||||
}
|
||||
val tunnels = futureTunnels.mapNotNull {
|
||||
try {
|
||||
it.await()
|
||||
} catch (e: Throwable) {
|
||||
throwables.add(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main.immediate) { onTunnelImportFinished(tunnels, throwables, messageCallback) }
|
||||
} catch (e: Throwable) {
|
||||
withContext(Dispatchers.Main.immediate) { onTunnelImportFinished(emptyList(), listOf(e), messageCallback) }
|
||||
}
|
||||
}
|
||||
|
||||
fun importTunnel(parentFragmentManager: FragmentManager, configText: String, messageCallback: (CharSequence) -> Unit) {
|
||||
try {
|
||||
// Ensure the config text is parseable before proceeding…
|
||||
Config.parse(ByteArrayInputStream(configText.toByteArray(StandardCharsets.UTF_8)))
|
||||
|
||||
// Config text is valid, now create the tunnel…
|
||||
ConfigNamingDialogFragment.newInstance(configText).show(parentFragmentManager, null)
|
||||
} catch (e: Throwable) {
|
||||
onTunnelImportFinished(emptyList(), listOf<Throwable>(e), messageCallback)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onTunnelImportFinished(tunnels: List<ObservableTunnel>, throwables: Collection<Throwable>, messageCallback: (CharSequence) -> Unit) {
|
||||
val context = Application.get().applicationContext
|
||||
var message = ""
|
||||
for (throwable in throwables) {
|
||||
val error = ErrorMessages[throwable]
|
||||
message = context.getString(R.string.import_error, error)
|
||||
Log.e(TAG, message, throwable)
|
||||
}
|
||||
if (tunnels.size == 1 && throwables.isEmpty())
|
||||
message = context.getString(R.string.import_success, tunnels[0].name)
|
||||
else if (tunnels.isEmpty() && throwables.size == 1)
|
||||
else if (throwables.isEmpty())
|
||||
message = context.resources.getQuantityString(R.plurals.import_total_success,
|
||||
tunnels.size, tunnels.size)
|
||||
else if (!throwables.isEmpty())
|
||||
message = context.resources.getQuantityString(R.plurals.import_partial_success,
|
||||
tunnels.size + throwables.size,
|
||||
tunnels.size, tunnels.size + throwables.size)
|
||||
|
||||
messageCallback(message)
|
||||
}
|
||||
|
||||
private const val TAG = "WireGuard/TunnelImporter"
|
||||
}
|
||||
@@ -1,59 +1,59 @@
|
||||
/*
|
||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.util
|
||||
|
||||
import androidx.datastore.preferences.edit
|
||||
import androidx.datastore.preferences.preferencesKey
|
||||
import androidx.datastore.preferences.preferencesSetKey
|
||||
import androidx.datastore.preferences.remove
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringSetPreferencesKey
|
||||
import com.wireguard.android.Application
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
object UserKnobs {
|
||||
private val DISABLE_KERNEL_MODULE = preferencesKey<Boolean>("disable_kernel_module")
|
||||
val disableKernelModule: Flow<Boolean>
|
||||
private val ENABLE_KERNEL_MODULE = booleanPreferencesKey("enable_kernel_module")
|
||||
val enableKernelModule: Flow<Boolean>
|
||||
get() = Application.getPreferencesDataStore().data.map {
|
||||
it[DISABLE_KERNEL_MODULE] ?: false
|
||||
it[ENABLE_KERNEL_MODULE] ?: false
|
||||
}
|
||||
|
||||
suspend fun setDisableKernelModule(disable: Boolean?) {
|
||||
suspend fun setEnableKernelModule(enable: Boolean?) {
|
||||
Application.getPreferencesDataStore().edit {
|
||||
if (disable == null)
|
||||
it.remove(DISABLE_KERNEL_MODULE)
|
||||
if (enable == null)
|
||||
it.remove(ENABLE_KERNEL_MODULE)
|
||||
else
|
||||
it[DISABLE_KERNEL_MODULE] = disable
|
||||
it[ENABLE_KERNEL_MODULE] = enable
|
||||
}
|
||||
}
|
||||
|
||||
private val MULTIPLE_TUNNELS = preferencesKey<Boolean>("multiple_tunnels")
|
||||
private val MULTIPLE_TUNNELS = booleanPreferencesKey("multiple_tunnels")
|
||||
val multipleTunnels: Flow<Boolean>
|
||||
get() = Application.getPreferencesDataStore().data.map {
|
||||
it[MULTIPLE_TUNNELS] ?: false
|
||||
}
|
||||
|
||||
private val DARK_THEME = preferencesKey<Boolean>("dark_theme")
|
||||
private val DARK_THEME = booleanPreferencesKey("dark_theme")
|
||||
val darkTheme: Flow<Boolean>
|
||||
get() = Application.getPreferencesDataStore().data.map {
|
||||
it[DARK_THEME] ?: false
|
||||
}
|
||||
|
||||
private val ALLOW_REMOTE_CONTROL_INTENTS = preferencesKey<Boolean>("allow_remote_control_intents")
|
||||
private val ALLOW_REMOTE_CONTROL_INTENTS = booleanPreferencesKey("allow_remote_control_intents")
|
||||
val allowRemoteControlIntents: Flow<Boolean>
|
||||
get() = Application.getPreferencesDataStore().data.map {
|
||||
it[ALLOW_REMOTE_CONTROL_INTENTS] ?: false
|
||||
}
|
||||
|
||||
private val RESTORE_ON_BOOT = preferencesKey<Boolean>("restore_on_boot")
|
||||
private val RESTORE_ON_BOOT = booleanPreferencesKey("restore_on_boot")
|
||||
val restoreOnBoot: Flow<Boolean>
|
||||
get() = Application.getPreferencesDataStore().data.map {
|
||||
it[RESTORE_ON_BOOT] ?: false
|
||||
}
|
||||
|
||||
private val LAST_USED_TUNNEL = preferencesKey<String>("last_used_tunnel")
|
||||
private val LAST_USED_TUNNEL = stringPreferencesKey("last_used_tunnel")
|
||||
val lastUsedTunnel: Flow<String?>
|
||||
get() = Application.getPreferencesDataStore().data.map {
|
||||
it[LAST_USED_TUNNEL]
|
||||
@@ -68,7 +68,7 @@ object UserKnobs {
|
||||
}
|
||||
}
|
||||
|
||||
private val RUNNING_TUNNELS = preferencesSetKey<String>("enabled_configs")
|
||||
private val RUNNING_TUNNELS = stringSetPreferencesKey("enabled_configs")
|
||||
val runningTunnels: Flow<Set<String>>
|
||||
get() = Application.getPreferencesDataStore().data.map {
|
||||
it[RUNNING_TUNNELS] ?: emptySet()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user