Compare commits

...

46 commits

Author SHA1 Message Date
Leo Heitmann Ruiz
222d9d90bc
Add icon for F-Droid (#79)
Some checks failed
Build / Build (push) Has been cancelled
2025-02-10 23:19:27 +00:00
Revertron
6ddc878fde
Prepared the 0.5.12 release. (#77)
* Prepared the 0.5.12 release.
2025-01-08 20:56:34 +01:00
Revertron
055a74ea69
UI improvements (#74)
* Fixed connected peers status in PeersActivity.
* Fixed display of new generated public key in SettingsActivity.
* Made more reliable display of connectivity state on main screen.
* Added a note about not configured peers. Changed all dialog buttons to greenish color.
* Click on a version row now opens URL of the project on GitHub.
* Changed the wording of no peer notification.
2025-01-08 20:13:56 +01:00
Poussinou
94db1facd7
Update readme.md (#41)
Signed-off-by: Poussinou <fravincent77@yahoo.fr>
Co-authored-by: Revertron <105154+Revertron@users.noreply.github.com>
2024-11-25 19:26:15 +01:00
Revertron
e211111d60
Updated release-notes for F-Droid. (#69) 2024-10-23 19:11:31 +02:00
Revertron
cdc12a8e7e
Prepare for release 0.5.9 (#68)
Updated the yggdrasil-go submodule. Fixed #64, updated some deps.
2024-10-23 18:10:01 +02:00
Revertron
34756b2193
Returned the yggdrasil-go submodule. (#63)
* Returned the yggdrasil-go submodule.
2024-08-19 00:07:45 +02:00
Revertron
060c096cdf
Preparation for release 0.5.7 (#62)
* Updated DNS.
* Removed tree info from main screen.
* Added a link to public peers site.
* Updated version and changelogs.
2024-08-09 00:24:22 +02:00
Neil Alexander
6a66960666
Version 0.1-017 (Yggdrasil 0.5.6) 2024-05-31 23:49:23 +01:00
Neil Alexander
a70563fba2
Check out Yggdrasil master branch instead of develop 2024-05-31 23:32:38 +01:00
Neil Alexander
ca45b37baa
Use peer count to track enabled/connected, remove unused submodule 2024-05-31 23:32:03 +01:00
Vasyl Gello
d1ebc977fd
Update core library and tag new version 0.1-016 (#53)
* Bump yggdrasil-go submodule to 0.5.4
* Version 0.1-016
2023-11-29 21:49:24 +01:00
Revertron
f164e05e5c
Updated graddle and some deps. (#54)
* Updated graddle and some deps.

* Updated Java version for CI (11 -> 17).
2023-11-29 21:42:06 +01:00
Revertron
8dfa2f638a
One fix and updates for F-Droid. (#50)
Fixed password getter (crashes for old configs).
Changed publishing material for F-Droid.
Incremented version.
2023-10-29 14:42:37 +01:00
Revertron
f4e1a75cfc
Changes for updating to Yggdrasil 0.5. (#49) 2023-10-28 23:31:32 +02:00
Revertron
9df80c0612
Fixed UI for Android TV (for d-pad usage). (#47) 2023-09-08 14:48:52 +02:00
Revertron
ce50b01781
Fixed the writer thread. (#42)
* Fixed the writer thread.
* Version increment.
* Added todo and info.
2022-12-29 19:59:19 +01:00
Revertron
05de180066
Implemented an option to reset keys (and IP). (#40)
* Implemented an option to reset keys (and IP).
* Implemented an option to set own keys (and IP).
* Incremented app version.
2022-12-14 12:37:45 +01:00
Revertron
a07412d02d
Implemented Yggdrasil start after device boot (#39)
Implemented Yggdrasil start after device boot (even if Always-On VPN disabled).
2022-12-14 11:41:23 +01:00
Revertron
055aab328d
Made description for fastlane shorter. (#37) 2022-11-23 20:37:01 +01:00
Revertron
7af870f34d
Added fastlane meta for inclusion in F-Droid, incremented version, fixed 1 string. (#35)
* Added fastlane meta for inclusion in F-Droid, incremented version, fixed 1 string.
* Added yggdrasil-go submodule for F-Droid pipeline.
* Changed logo images to black.
2022-11-23 14:23:54 +01:00
Revertron
b472c7283f
Made better collapsed notification with status. (#34) 2022-11-19 22:53:01 +01:00
Revertron
aa94ccad26
Fixes #30, added quick settings icon. (#32)
* Fixes #30, added quick settings icon.
* Added saving of enabled state, added some fixes and refactorings.
* Fixed a bug with turning on/off the VPN.
* Fixed possibility to add duplicate servers in DNS settings.
2022-11-19 20:49:34 +01:00
Revertron
ee81f4e902
UI fixes, DNS infos. (#31) 2022-11-16 22:08:28 +01:00
Revertron
c9476a7b00
Made fast reconnect when network becomes up. (#28)
* Made fast reconnect when network becomes up.
* Use `retryPeersNow`
2022-11-12 12:37:40 +01:00
Revertron
41569a9ee2
Optimization and localizations. (#27)
* Optimized UI refresh to save battery. Extracted all strings to xml to enable localizations.
* And added Russian localization.
2022-11-07 23:39:35 +01:00
Revertron
8615d43761
Optimized packet buffers. (#26) 2022-11-01 13:43:35 +01:00
Neil Alexander
aa725fbf8f
Build 009 version code 2022-10-30 23:03:25 +00:00
Neil Alexander
de7cfce1b6
Upload artifacts to releases in CI
Signed-off-by: Neil Alexander <neilalexander@users.noreply.github.com>
2022-10-30 22:39:15 +00:00
Neil Alexander
c0032e23dc
MIT License
Closes #12
2022-10-30 22:14:13 +00:00
David Adrião
87c6e4612b
Added copy to clipboard of several fields. (#17)
* Added copy to clipboard when clicking ip address, subnet address and public key.

* Android CI

* Update CI

* Update `PATH` in CI

* Add NDK in CI

* Fix filename in CI

* Capture artifact in CI

* Tweaks in CI

* Update MainActivity.kt

* Update SettingsActivity.kt

Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
2022-10-30 21:47:53 +00:00
Neil Alexander
24bcee7934
Update CI 2022-10-30 21:35:00 +00:00
Neil Alexander
6771177ca9
Update DNS server strings 2022-10-30 21:23:34 +00:00
Revertron
772dfdef8c
Added DNS configuration functionality. (#24)
* Changed app icon from default to Yggdrasil leaf.

* Added workaround for DNS-reslver and fix for unmetered networks.

* Added DNS configuration functionality.

* Changed DNS configuration UI. Disabled DNS config by default. Added DNS fix for Chrome-based browsers.
2022-10-30 21:14:30 +00:00
Neil Alexander
24573625a4
Sign CI builds 2022-10-30 00:27:23 +01:00
Neil Alexander
a0e010992e
CI 2022-10-29 23:26:14 +01:00
Revertron
8e74ea8ca7
Added workaround for DNS-reslver and fix for unmetered networks. (#22)
* Changed app icon from default to Yggdrasil leaf.

* Added workaround for DNS-reslver and fix for unmetered networks.
2022-10-28 13:17:55 +01:00
Revertron
796096b9d8
Changed app icon from default to Yggdrasil leaf. (#21) 2022-10-28 12:16:49 +01:00
Neil Alexander
7e4321b6c5
Merge pull request #20 from majestrate/add-readme-2022-10-19
add readme.md
2022-10-19 13:55:01 +01:00
Jeff Becker
609466fac1
add readme.md
this contains build instructions on how to build this project
2022-10-19 08:53:14 -04:00
Neil Alexander
d10bb853a6 Build 8 (improve always-on VPN) 2021-10-29 23:45:30 +01:00
Neil Alexander
5887ae47b2 Build 6
Fixes #8
Fixes #6
2021-08-21 22:18:40 +01:00
Neil Alexander
27e1909aa2 Build 5 2021-06-28 18:51:42 +01:00
Neil Alexander
fa9532fb97 Settings screen, display fixes 2021-06-24 18:12:16 +01:00
Neil Alexander
8f3ee30e74 v0.4.0rc3 2021-06-19 18:24:22 +01:00
Neil Alexander
b57bb71d65 Hopefully stop internet traffic being blocked 2021-06-16 23:11:03 +01:00
94 changed files with 3196 additions and 1023 deletions

90
.github/workflows/android.yml vendored Normal file
View file

@ -0,0 +1,90 @@
name: Build
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
workflow_dispatch:
release:
types: [published]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Check out
uses: actions/checkout@v3
- name: Check out Yggdrasil
uses: actions/checkout@v3
with:
repository: yggdrasil-network/yggdrasil-go
path: yggdrasil-go
ref: master
fetch-depth: 0
- name: Setup Go environment
uses: actions/setup-go@v3.3.1
- name: Install gomobile
run: |
go install golang.org/x/mobile/cmd/gomobile@latest
~/go/bin/gomobile init
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: gradle
- name: Install NDK
uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r21e
add-to-path: false
- name: Build Yggdrasil
run: |
mkdir app/libs
cd yggdrasil-go
PATH=$PATH:~/go/bin/ ./contrib/mobile/build -a
cp {yggdrasil.aar,yggdrasil-sources.jar} ../app/libs
env:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
- name: Gradle build
if: github.event_name != 'release' && github.ref_name != 'main'
run: |
chmod +x gradlew
./gradlew buildRelease
- name: Gradle signed build
if: github.event_name == 'release' || github.ref_name == 'main'
run: |
echo "${{ secrets.RELEASE_KEYSTORE }}" > app/gha.keystore.asc
gpg -d --passphrase "${{ secrets.RELEASE_KEYSTORE_PASSWORD }}" --batch app/gha.keystore.asc > app/gha.jks
chmod +x gradlew
./gradlew assembleYggdrasil
- name: Upload build artifact
if: github.event_name == 'release' || github.ref_name == 'main'
uses: actions/upload-artifact@v3
with:
name: yggdrasil-android
path: app/build/outputs/apk/yggdrasil/app-yggdrasil.apk
- name: Upload release artifact
if: github.event_name == 'release'
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: app/build/outputs/apk/yggdrasil/app-yggdrasil.apk
asset_name: yggdrasil-android.apk
asset_content_type: application/vnd.android.package-archive

3
.gitignore vendored
View file

@ -1,3 +1,4 @@
*.apk
*.iml *.iml
.gradle .gradle
/local.properties /local.properties
@ -15,3 +16,5 @@
local.properties local.properties
/app/libs/yggdrasil.aar /app/libs/yggdrasil.aar
/app/libs/yggdrasil-sources.jar /app/libs/yggdrasil-sources.jar
/app/release/*
/app/release

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "libs/yggdrasil-go"]
path = libs/yggdrasil-go
url = https://github.com/yggdrasil-network/yggdrasil-go

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Yggdrasil Developers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -4,24 +4,39 @@ plugins {
} }
android { android {
compileSdkVersion 29 compileSdkVersion 34
buildToolsVersion "30.0.3"
defaultConfig { defaultConfig {
applicationId "eu.neilalexander.yggdrasil" applicationId "eu.neilalexander.yggdrasil"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 29 targetSdkVersion 34
versionCode 1 versionCode 20
versionName "0.1" versionName "0.1-020"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
signingConfigs {
create("yggdrasil") {
// You need to specify either an absolute path or include the
// keystore file in the same directory as the build.gradle file.
storeFile = file("gha.jks")
storePassword = "g1thub4ct10n34yggdr4s1l4ndr01d"
keyAlias = "yggdrasil-android"
keyPassword = "g1thub4ct10n34yggdr4s1l4ndr01d"
}
}
buildTypes { buildTypes {
release { release {
minifyEnabled false minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
yggdrasil {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig = signingConfigs.getByName("yggdrasil")
}
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
@ -30,16 +45,18 @@ android {
kotlinOptions { kotlinOptions {
jvmTarget = '1.8' jvmTarget = '1.8'
} }
namespace 'eu.neilalexander.yggdrasil'
} }
dependencies { dependencies {
implementation fileTree(include: ['*.aar'], dir: 'libs') implementation fileTree(include: ['*.aar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.5.0' implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'com.google.android.material:material:1.3.0' implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.+' implementation 'androidx.preference:preference-ktx:1.2.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.ext:junit:1.2.1'
} androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
}

Binary file not shown.

View file

@ -1,18 +0,0 @@
{
"version": 2,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "eu.neilalexander.yggdrasil",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"versionCode": 1,
"versionName": "1.0",
"outputFile": "app-release.apk"
}
]
}

View file

@ -1,37 +1,72 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android">
package="eu.neilalexander.yggdrasil">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
<application <application
android:name=".GlobalApplication" android:name=".GlobalApplication"
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@drawable/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@drawable/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.Yggdrasil"> android:theme="@style/Theme.Yggdrasil"
<activity android:name=".SettingsActivity" android:largeHeap="true">
android:parentActivityName=".MainActivity" /> <activity android:name=".MainActivity" android:exported="true">
<activity android:name=".PeersActivity"
android:parentActivityName=".MainActivity" />
<activity android:name=".MainActivity">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".SettingsActivity" android:parentActivityName=".MainActivity" android:exported="false" />
<activity android:name=".PeersActivity" android:parentActivityName=".MainActivity" android:exported="false" />
<activity android:name=".DnsActivity" android:exported="false" />
<activity android:name=".TileServiceActivity" android:theme="@android:style/Theme.NoDisplay"
android:allowTaskReparenting="true"
android:alwaysRetainTaskState="false"
android:clearTaskOnLaunch="true"
android:enabled="true"
android:exported="true"
android:excludeFromRecents="true"
android:finishOnTaskLaunch="true">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
</intent-filter>
</activity>
<service <service
android:name=".PacketTunnelProvider" android:name=".PacketTunnelProvider"
android:permission="android.permission.BIND_VPN_SERVICE"> android:permission="android.permission.BIND_VPN_SERVICE"
android:foregroundServiceType="systemExempted"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.net.VpnService" /> <action android:name="android.net.VpnService" />
</intent-filter> </intent-filter>
</service> </service>
<service
android:name=".YggTileService"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
android:icon="@drawable/ic_tile_icon"
android:label="@string/app_name"
android:exported="true">
<meta-data android:name="android.service.quicksettings.ACTIVE_TILE" android:value="true" />
<meta-data android:name="android.service.quicksettings.TOGGLEABLE_TILE" android:value="true" />
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
<receiver android:name=".BootUpReceiver" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application> </application>
</manifest> </manifest>

View file

@ -0,0 +1,40 @@
package eu.neilalexander.yggdrasil
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.net.VpnService
import android.util.Log
import androidx.preference.PreferenceManager
class BootUpReceiver : BroadcastReceiver() {
companion object {
const val TAG = "BootUpReceiver"
}
override fun onReceive(context: Context, intent: Intent) {
if (intent.action != Intent.ACTION_BOOT_COMPLETED) {
Log.w(TAG, "Wrong action: ${intent.action}")
}
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
if (!preferences.getBoolean(PREF_KEY_ENABLED, false)) {
Log.i(TAG, "Yggdrasil disabled, not starting service")
return
}
Log.i(TAG, "Yggdrasil enabled, starting service")
val serviceIntent = Intent(context, PacketTunnelProvider::class.java)
serviceIntent.action = PacketTunnelProvider.ACTION_START
val vpnIntent = VpnService.prepare(context)
if (vpnIntent != null) {
Log.i(TAG, "Need to ask for VPN permission")
val notification = createPermissionMissingNotification(context)
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.notify(444, notification)
} else {
context.startService(serviceIntent)
}
}
}

View file

@ -2,6 +2,7 @@ package eu.neilalexander.yggdrasil
import android.content.Context import android.content.Context
import mobile.Mobile import mobile.Mobile
import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import java.io.File import java.io.File
@ -21,6 +22,25 @@ object ConfigurationProxy {
return this return this
} }
fun resetJSON() {
val conf = Mobile.generateConfigJSON()
file.writeBytes(conf)
fix()
}
fun resetKeys() {
val newJson = JSONObject(String(Mobile.generateConfigJSON()))
updateJSON { json ->
json.put("PrivateKey", newJson.getString("PrivateKey"))
}
}
fun setKeys(privateKey: String) {
updateJSON { json ->
json.put("PrivateKey", privateKey)
}
}
fun updateJSON(fn: (JSONObject) -> Unit) { fun updateJSON(fn: (JSONObject) -> Unit) {
json = JSONObject(file.readText(Charsets.UTF_8)) json = JSONObject(file.readText(Charsets.UTF_8))
fn(json) fn(json)
@ -33,6 +53,19 @@ object ConfigurationProxy {
json.put("AdminListen", "none") json.put("AdminListen", "none")
json.put("IfName", "none") json.put("IfName", "none")
json.put("IfMTU", 65535) json.put("IfMTU", 65535)
if (json.getJSONArray("MulticastInterfaces").get(0) is String) {
val ar = JSONArray()
ar.put(0, JSONObject("""
{
"Regex": ".*",
"Beacon": true,
"Listen": true,
"Password": ""
}
""".trimIndent()))
json.put("MulticastInterfaces", ar)
}
} }
} }
@ -44,4 +77,28 @@ object ConfigurationProxy {
fun getJSONByteArray(): ByteArray { fun getJSONByteArray(): ByteArray {
return json.toString().toByteArray(Charsets.UTF_8) return json.toString().toByteArray(Charsets.UTF_8)
} }
var multicastListen: Boolean
get() = (json.getJSONArray("MulticastInterfaces").get(0) as JSONObject).getBoolean("Listen")
set(value) {
updateJSON { json ->
(json.getJSONArray("MulticastInterfaces").get(0) as JSONObject).put("Listen", value)
}
}
var multicastBeacon: Boolean
get() = (json.getJSONArray("MulticastInterfaces").get(0) as JSONObject).getBoolean("Beacon")
set(value) {
updateJSON { json ->
(json.getJSONArray("MulticastInterfaces").get(0) as JSONObject).put("Beacon", value)
}
}
var multicastPassword: String
get() = (json.getJSONArray("MulticastInterfaces").get(0) as JSONObject).optString("Password")
set(value) {
updateJSON { json ->
(json.getJSONArray("MulticastInterfaces").get(0) as JSONObject).put("Password", value)
}
}
} }

View file

@ -0,0 +1,192 @@
package eu.neilalexander.yggdrasil
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.SharedPreferences
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.view.View
import android.widget.*
import androidx.preference.PreferenceManager
import com.google.android.material.textfield.TextInputEditText
const val KEY_DNS_SERVERS = "dns_servers"
const val KEY_DNS_VERSION = "dns_version"
const val KEY_ENABLE_CHROME_FIX = "enable_chrome_fix"
class DnsActivity : AppCompatActivity() {
private lateinit var config: ConfigurationProxy
private lateinit var inflater: LayoutInflater
private lateinit var serversTableLayout: TableLayout
private lateinit var serversTableLabel: TextView
private lateinit var serversTableHint: TextView
private lateinit var addServerButton: ImageButton
private lateinit var enableChromeFix: Switch
private lateinit var servers: MutableList<String>
private lateinit var preferences: SharedPreferences
private lateinit var defaultDnsServers: HashMap<String, Pair<String, String>>
@SuppressLint("ApplySharedPref")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_dns)
config = ConfigurationProxy(applicationContext)
inflater = LayoutInflater.from(this)
val descriptionRevertron = getString(R.string.dns_server_info_revertron)
// Here we can add some other DNS servers in a future
defaultDnsServers = hashMapOf(
"308:62:45:62::" to Pair(getString(R.string.location_amsterdam), descriptionRevertron),
"308:84:68:55::" to Pair(getString(R.string.location_frankfurt), descriptionRevertron),
"308:25:40:bd::" to Pair(getString(R.string.location_bratislava), descriptionRevertron),
"308:c8:48:45::" to Pair(getString(R.string.location_buffalo), descriptionRevertron),
)
serversTableLayout = findViewById(R.id.configuredDnsTableLayout)
serversTableLabel = findViewById(R.id.configuredDnsLabel)
serversTableHint = findViewById(R.id.configuredDnsHint)
enableChromeFix = findViewById(R.id.enableChromeFix)
addServerButton = findViewById(R.id.addServerButton)
addServerButton.setOnClickListener {
val view = inflater.inflate(R.layout.dialog_add_dns_server, null)
val input = view.findViewById<TextInputEditText>(R.id.addDnsInput)
val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.YggdrasilDialogs))
builder.setTitle(getString(R.string.dns_add_server_dialog_title))
builder.setView(view)
builder.setPositiveButton(getString(R.string.add)) { _, _ ->
val server = input.text.toString()
if (!servers.contains(server)) {
servers.add(server)
preferences.edit().apply {
putString(KEY_DNS_SERVERS, servers.joinToString(","))
commit()
}
updateConfiguredServers()
} else {
Toast.makeText(this, R.string.dns_already_added_server, Toast.LENGTH_SHORT).show()
}
}
builder.setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
dialog.cancel()
}
builder.show()
}
enableChromeFix.setOnCheckedChangeListener { _, isChecked ->
preferences.edit().apply {
putBoolean(KEY_ENABLE_CHROME_FIX, isChecked)
commit()
}
}
val enableChromeFixPanel = findViewById<TableRow>(R.id.enableChromeFixPanel)
enableChromeFixPanel.setOnClickListener {
enableChromeFix.toggle()
}
preferences = PreferenceManager.getDefaultSharedPreferences(this.baseContext)
val serverString = preferences.getString(KEY_DNS_SERVERS, "")
servers = if (serverString!!.isNotEmpty()) {
serverString.split(",").toMutableList()
} else {
mutableListOf()
}
updateUsableServers()
}
override fun onResume() {
super.onResume()
updateConfiguredServers()
enableChromeFix.isChecked = preferences.getBoolean(KEY_ENABLE_CHROME_FIX, false)
}
@SuppressLint("ApplySharedPref")
private fun updateConfiguredServers() {
when (servers.size) {
0 -> {
serversTableLayout.visibility = View.GONE
serversTableLabel.text = getString(R.string.dns_no_configured_servers)
serversTableHint.text = getText(R.string.dns_configured_servers_hint_empty)
}
else -> {
serversTableLayout.visibility = View.VISIBLE
serversTableLabel.text = getString(R.string.dns_configured_servers)
serversTableHint.text = getText(R.string.dns_configured_servers_hint)
serversTableLayout.removeAllViewsInLayout()
for (i in servers.indices) {
val server = servers[i]
val view = inflater.inflate(R.layout.peers_configured, null)
view.findViewById<TextView>(R.id.addressValue).text = server
view.findViewById<ImageButton>(R.id.deletePeerButton).tag = i
view.findViewById<ImageButton>(R.id.deletePeerButton).setOnClickListener { button ->
val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.YggdrasilDialogs))
builder.setTitle(getString(R.string.dns_remove_title, server))
builder.setPositiveButton(getString(R.string.remove)) { dialog, _ ->
servers.removeAt(button.tag as Int)
preferences.edit().apply {
this.putString(KEY_DNS_SERVERS, servers.joinToString(","))
this.commit()
}
dialog.dismiss()
updateConfiguredServers()
}
builder.setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
dialog.cancel()
}
builder.show()
}
serversTableLayout.addView(view)
}
}
}
}
@SuppressLint("ApplySharedPref")
private fun updateUsableServers() {
val usableTableLayout: TableLayout = findViewById(R.id.usableDnsTableLayout)
defaultDnsServers.forEach {
val server = it.key
val infoPair = it.value
val view = inflater.inflate(R.layout.dns_server_usable, null)
view.findViewById<TextView>(R.id.serverValue).text = server
val addButton = view.findViewById<ImageButton>(R.id.addButton)
addButton.tag = server
addButton.setOnClickListener { button ->
val serverString = button.tag as String
if (!servers.contains(serverString)) {
servers.add(serverString)
preferences.edit().apply {
this.putString(KEY_DNS_SERVERS, servers.joinToString(","))
this.commit()
}
updateConfiguredServers()
} else {
Toast.makeText(this, R.string.dns_already_added_server, Toast.LENGTH_SHORT).show()
}
}
view.setOnLongClickListener {
val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.YggdrasilDialogs))
builder.setTitle(getString(R.string.dns_server_info_dialog_title))
builder.setMessage("${infoPair.first}\n\n${infoPair.second}")
builder.setPositiveButton(getString(R.string.ok)) { dialog, _ ->
dialog.dismiss()
}
builder.show()
true
}
usableTableLayout.addView(view)
}
}
}

View file

@ -1,25 +1,157 @@
package eu.neilalexander.yggdrasil package eu.neilalexander.yggdrasil
import android.app.Application import android.app.*
import android.content.IntentFilter import android.content.ComponentName
import androidx.localbroadcastmanager.content.LocalBroadcastManager import android.content.Context
import android.content.Intent
import android.os.Build
import android.service.quicksettings.TileService
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.preference.PreferenceManager
class GlobalApplication: Application() { const val PREF_KEY_ENABLED = "enabled"
private var state = PacketTunnelState const val PREF_KEY_PEERS_NOTE = "peers_note"
const val MAIN_CHANNEL_ID = "Yggdrasil Service"
class GlobalApplication: Application(), YggStateReceiver.StateReceiver {
private lateinit var config: ConfigurationProxy private lateinit var config: ConfigurationProxy
private var currentState: State = State.Disabled
private var updaterConnections: Int = 0
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
config = ConfigurationProxy(applicationContext) config = ConfigurationProxy(applicationContext)
val callback = NetworkStateCallback(this)
LocalBroadcastManager.getInstance(this).registerReceiver( callback.register()
state, IntentFilter(PacketTunnelProvider.RECEIVER_INTENT) val receiver = YggStateReceiver(this)
) receiver.register(this)
migrateDnsServers(this)
} }
override fun onTerminate() { fun subscribe() {
super.onTerminate() updaterConnections++
}
LocalBroadcastManager.getInstance(this).unregisterReceiver(state) fun unsubscribe() {
if (updaterConnections > 0) {
updaterConnections--
}
}
fun needUiUpdates(): Boolean {
return updaterConnections > 0
}
fun getCurrentState(): State {
return currentState
}
@RequiresApi(Build.VERSION_CODES.N)
override fun onStateChange(state: State) {
if (state != currentState) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val componentName = ComponentName(this, YggTileService::class.java)
TileService.requestListeningState(this, componentName)
}
if (state != State.Disabled) {
val notification = createServiceNotification(this, state)
val notificationManager: NotificationManager =
this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(SERVICE_NOTIFICATION_ID, notification)
}
currentState = state
}
}
}
fun migrateDnsServers(context: Context) {
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
if (preferences.getInt(KEY_DNS_VERSION, 0) >= 1) {
return
}
val serverString = preferences.getString(KEY_DNS_SERVERS, "")
if (serverString!!.isNotEmpty()) {
// Replacing old Revertron's servers by new ones
val newServers = serverString
.replace("300:6223::53", "308:25:40:bd::")
.replace("302:7991::53", "308:62:45:62::")
.replace("302:db60::53", "308:84:68:55::")
.replace("301:1088::53", "308:c8:48:45::")
val editor = preferences.edit()
editor.putInt(KEY_DNS_VERSION, 1)
if (newServers != serverString) {
editor.putString(KEY_DNS_SERVERS, newServers)
}
editor.apply()
}
}
fun createServiceNotification(context: Context, state: State): Notification {
createNotificationChannels(context)
val intent = Intent(context, MainActivity::class.java).apply {
this.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
var flags = PendingIntent.FLAG_UPDATE_CURRENT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, flags)
val text = when (state) {
State.Disabled -> context.getText(R.string.tile_disabled)
State.Enabled -> context.getText(R.string.tile_enabled)
State.Connected -> context.getText(R.string.tile_connected)
else -> context.getText(R.string.tile_disabled)
}
return NotificationCompat.Builder(context, MAIN_CHANNEL_ID)
.setShowWhen(false)
.setContentTitle(text)
.setSmallIcon(R.drawable.ic_tile_icon)
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_MIN)
.build()
}
fun createPermissionMissingNotification(context: Context): Notification {
createNotificationChannels(context)
val intent = Intent(context, MainActivity::class.java).apply {
this.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
var flags = PendingIntent.FLAG_UPDATE_CURRENT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, flags)
return NotificationCompat.Builder(context, MAIN_CHANNEL_ID)
.setShowWhen(false)
.setContentTitle(context.getText(R.string.app_name))
.setContentText(context.getText(R.string.permission_notification_text))
.setSmallIcon(R.drawable.ic_tile_icon)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.build()
}
private fun createNotificationChannels(context: Context) {
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = context.getString(R.string.channel_name)
val descriptionText = context.getString(R.string.channel_description)
val importance = NotificationManager.IMPORTANCE_MIN
val channel = NotificationChannel(MAIN_CHANNEL_ID, name, importance).apply {
description = descriptionText
}
// Register the channel with the system
val notificationManager: NotificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
} }
} }

View file

@ -1,32 +1,51 @@
package eu.neilalexander.yggdrasil package eu.neilalexander.yggdrasil
import android.content.BroadcastReceiver import android.app.Activity
import android.content.Context import android.app.AlertDialog
import android.content.Intent import android.content.*
import android.content.IntentFilter
import android.graphics.Color import android.graphics.Color
import android.net.Uri
import android.net.VpnService import android.net.VpnService
import android.os.Bundle import android.os.Bundle
import android.view.ContextThemeWrapper
import android.widget.Switch import android.widget.Switch
import android.widget.TableRow
import android.widget.TextView import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.LinearLayoutCompat
import androidx.core.content.edit
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager
import eu.neilalexander.yggdrasil.PacketTunnelProvider.Companion.STATE_INTENT
import mobile.Mobile import mobile.Mobile
import org.json.JSONArray import org.json.JSONArray
const val APP_WEB_URL = "https://github.com/yggdrasil-network/yggdrasil-android"
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private var state = PacketTunnelState
private lateinit var enabledSwitch: Switch private lateinit var enabledSwitch: Switch
private lateinit var enabledLabel: TextView private lateinit var enabledLabel: TextView
private lateinit var ipAddressLabel: TextView private lateinit var ipAddressLabel: TextView
private lateinit var subnetLabel: TextView private lateinit var subnetLabel: TextView
private lateinit var coordinatesLabel: TextView
private lateinit var peersLabel: TextView private lateinit var peersLabel: TextView
private lateinit var peersRow: TableRow private lateinit var peersRow: LinearLayoutCompat
private lateinit var settingsRow: TableRow private lateinit var dnsLabel: TextView
private lateinit var dnsRow: LinearLayoutCompat
private lateinit var settingsRow: LinearLayoutCompat
private lateinit var versionRow: LinearLayoutCompat
private fun start() {
val intent = Intent(this, PacketTunnelProvider::class.java)
intent.action = PacketTunnelProvider.ACTION_START
startService(intent)
}
private var startVpnActivity = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
start()
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -34,27 +53,28 @@ class MainActivity : AppCompatActivity() {
findViewById<TextView>(R.id.versionValue).text = Mobile.getVersion() findViewById<TextView>(R.id.versionValue).text = Mobile.getVersion()
enabledSwitch = findViewById(R.id.enableMulticastSwitch) enabledSwitch = findViewById(R.id.enableYggdrasil)
enabledLabel = findViewById(R.id.yggdrasilStatusLabel) enabledLabel = findViewById(R.id.yggdrasilStatusLabel)
ipAddressLabel = findViewById(R.id.ipAddressValue) ipAddressLabel = findViewById(R.id.ipAddressValue)
subnetLabel = findViewById(R.id.subnetValue) subnetLabel = findViewById(R.id.subnetValue)
coordinatesLabel = findViewById(R.id.coordinatesValue)
peersLabel = findViewById(R.id.peersValue) peersLabel = findViewById(R.id.peersValue)
peersRow = findViewById(R.id.peersTableRow) peersRow = findViewById(R.id.peersTableRow)
dnsLabel = findViewById(R.id.dnsValue)
dnsRow = findViewById(R.id.dnsTableRow)
settingsRow = findViewById(R.id.settingsTableRow) settingsRow = findViewById(R.id.settingsTableRow)
versionRow = findViewById(R.id.versionTableRow)
enabledLabel.setTextColor(Color.GRAY) enabledLabel.setTextColor(Color.GRAY)
VpnService.prepare(this)
enabledSwitch.setOnCheckedChangeListener { _, isChecked -> enabledSwitch.setOnCheckedChangeListener { _, isChecked ->
when (isChecked) { when (isChecked) {
true -> { true -> {
val vpnintent = VpnService.prepare(this) val vpnIntent = VpnService.prepare(this)
if (vpnintent != null) { if (vpnIntent != null) {
startActivityForResult(vpnintent, 0) startVpnActivity.launch(vpnIntent)
} else { } else {
onActivityResult(0, RESULT_OK, vpnintent) start()
enabledSwitch.isEnabled = false
} }
} }
false -> { false -> {
@ -63,6 +83,13 @@ class MainActivity : AppCompatActivity() {
startService(intent) startService(intent)
} }
} }
val preferences = PreferenceManager.getDefaultSharedPreferences(this.baseContext)
preferences.edit(commit = true) { putBoolean(PREF_KEY_ENABLED, isChecked) }
}
val enableYggdrasilPanel = findViewById<LinearLayoutCompat>(R.id.enableYggdrasilPanel)
enableYggdrasilPanel.setOnClickListener {
enabledSwitch.toggle()
} }
peersRow.isClickable = true peersRow.isClickable = true
@ -71,62 +98,140 @@ class MainActivity : AppCompatActivity() {
startActivity(intent) startActivity(intent)
} }
dnsRow.isClickable = true
dnsRow.setOnClickListener {
val intent = Intent(this, DnsActivity::class.java)
startActivity(intent)
}
settingsRow.isClickable = true settingsRow.isClickable = true
settingsRow.setOnClickListener { settingsRow.setOnClickListener {
val intent = Intent(this, SettingsActivity::class.java) val intent = Intent(this, SettingsActivity::class.java)
startActivity(intent) startActivity(intent)
} }
versionRow.isClickable = true
versionRow.setOnClickListener {
openUrlInBrowser(APP_WEB_URL)
}
ipAddressLabel.setOnLongClickListener {
val clipboard: ClipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("ip", ipAddressLabel.text)
clipboard.setPrimaryClip(clip)
Toast.makeText(applicationContext,R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
true
}
subnetLabel.setOnLongClickListener {
val clipboard: ClipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("subnet", subnetLabel.text)
clipboard.setPrimaryClip(clip)
Toast.makeText(applicationContext,R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
true
}
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
LocalBroadcastManager.getInstance(this).registerReceiver( LocalBroadcastManager.getInstance(this).registerReceiver(
receiver, IntentFilter(PacketTunnelState.RECEIVER_INTENT) receiver, IntentFilter(STATE_INTENT)
) )
val preferences = PreferenceManager.getDefaultSharedPreferences(this.baseContext)
enabledSwitch.isChecked = preferences.getBoolean(PREF_KEY_ENABLED, false)
val serverString = preferences.getString(KEY_DNS_SERVERS, "")
if (serverString!!.isNotEmpty()) {
val servers = serverString.split(",")
dnsLabel.text = when (servers.size) {
0 -> getString(R.string.dns_no_servers)
1 -> getString(R.string.dns_one_server)
else -> getString(R.string.dns_many_servers, servers.size)
}
} else {
dnsLabel.text = getString(R.string.dns_no_servers)
}
(application as GlobalApplication).subscribe()
}
override fun onPause() {
super.onPause()
(application as GlobalApplication).unsubscribe()
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
} }
private val receiver: BroadcastReceiver = object : BroadcastReceiver() { private val receiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) { override fun onReceive(context: Context?, intent: Intent) {
when (intent.getStringExtra("type")) { when (intent.getStringExtra("type")) {
"state" -> { "state" -> {
val peerState = JSONArray(intent.getStringExtra("peers") ?: "[]")
var count = 0
for (i in 0..<peerState.length()) {
val peer = peerState.getJSONObject(i)
if (peer.getString("IP").isNotEmpty()) {
count += 1
}
}
enabledLabel.text = if (intent.getBooleanExtra("started", false)) { enabledLabel.text = if (intent.getBooleanExtra("started", false)) {
if (state.dhtCount() == 0) { showPeersNoteIfNeeded(peerState.length())
if (count == 0) {
enabledLabel.setTextColor(Color.RED) enabledLabel.setTextColor(Color.RED)
"No connectivity" getString(R.string.main_no_connectivity)
} else { } else {
enabledLabel.setTextColor(Color.GREEN) enabledLabel.setTextColor(Color.GREEN)
"Enabled" getString(R.string.main_enabled)
} }
} else { } else {
enabledLabel.setTextColor(Color.GRAY) enabledLabel.setTextColor(Color.GRAY)
"Not enabled" getString(R.string.main_disabled)
} }
ipAddressLabel.text = intent.getStringExtra("ip") ?: "N/A" ipAddressLabel.text = intent.getStringExtra("ip") ?: "N/A"
subnetLabel.text = intent.getStringExtra("subnet") ?: "N/A" subnetLabel.text = intent.getStringExtra("subnet") ?: "N/A"
coordinatesLabel.text = intent.getStringExtra("coords") ?: "[]" if (intent.hasExtra("peers")) {
peersLabel.text = when (val count = state.peerCount()) { peersLabel.text = when (count) {
0 -> "No peers" 0 -> getString(R.string.main_no_peers)
1 -> "1 peer" 1 -> getString(R.string.main_one_peer)
else -> "$count peers" else -> getString(R.string.main_many_peers, count)
}
}
if (!enabledSwitch.isEnabled) {
enabledSwitch.isEnabled = true
} }
} }
} }
} }
} }
override fun onPause() { private fun showPeersNoteIfNeeded(peerCount: Int) {
super.onPause() if (peerCount > 0) return
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver) val preferences = PreferenceManager.getDefaultSharedPreferences(this@MainActivity.baseContext)
} if (!preferences.getBoolean(PREF_KEY_PEERS_NOTE, false)) {
this@MainActivity.runOnUiThread {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { val builder: AlertDialog.Builder =
super.onActivityResult(requestCode, resultCode, data) AlertDialog.Builder(ContextThemeWrapper(this@MainActivity, R.style.YggdrasilDialogs))
when (resultCode) { builder.setTitle(getString(R.string.main_add_some_peers_title))
RESULT_OK -> { builder.setMessage(getString(R.string.main_add_some_peers_message))
val intent = Intent(this, PacketTunnelProvider::class.java) builder.setPositiveButton(getString(R.string.ok)) { dialog, _ ->
intent.action = PacketTunnelProvider.ACTION_START dialog.dismiss()
startService(intent) }
builder.show()
}
// Mark this note as shown
preferences.edit().apply {
putBoolean(PREF_KEY_PEERS_NOTE, true)
commit()
} }
} }
} }
}
fun openUrlInBrowser(url: String) {
val intent = Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse(url)
}
try {
startActivity(intent)
} catch (e: ActivityNotFoundException) {
// Handle the exception if no browser is found
Toast.makeText(this, getText(R.string.no_browser_found_toast), Toast.LENGTH_SHORT).show()
}
}
}

View file

@ -0,0 +1,52 @@
package eu.neilalexander.yggdrasil
import android.content.Context
import android.content.Intent
import android.net.*
import android.os.Build
import android.util.Log
import androidx.preference.PreferenceManager
private const val TAG = "Network"
class NetworkStateCallback(val context: Context) : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
Log.d(TAG, "onAvailable")
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
if (preferences.getBoolean(PREF_KEY_ENABLED, false)) {
Thread {
// The message often arrives before the connection is fully established
Thread.sleep(1000)
val intent = Intent(context, PacketTunnelProvider::class.java)
intent.action = PacketTunnelProvider.ACTION_CONNECT
try {
context.startService(intent)
} catch (e: IllegalStateException) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent)
}
}
}.start()
}
}
override fun onLost(network: Network) {
super.onLost(network)
Log.d(TAG, "onLost")
}
fun register() {
val request = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
.build()
val manager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
manager.registerNetworkCallback(request, this)
}
}

View file

@ -1,13 +1,15 @@
package eu.neilalexander.yggdrasil package eu.neilalexander.yggdrasil
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.net.VpnService import android.net.VpnService
import android.os.Handler import android.net.wifi.WifiManager
import android.os.Message import android.os.Build
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import android.system.OsConstants
import android.util.Log import android.util.Log
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager
import eu.neilalexander.yggdrasil.YggStateReceiver.Companion.YGG_STATE_INTENT
import mobile.Yggdrasil import mobile.Yggdrasil
import org.json.JSONArray import org.json.JSONArray
import java.io.FileInputStream import java.io.FileInputStream
@ -16,26 +18,32 @@ import java.util.concurrent.atomic.AtomicBoolean
import kotlin.concurrent.thread import kotlin.concurrent.thread
class PacketTunnelProvider: VpnService() { private const val TAG = "PacketTunnelProvider"
const val SERVICE_NOTIFICATION_ID = 1000
open class PacketTunnelProvider: VpnService() {
companion object { companion object {
const val RECEIVER_INTENT = "eu.neilalexander.yggdrasil.PacketTunnelProvider.MESSAGE" const val STATE_INTENT = "eu.neilalexander.yggdrasil.PacketTunnelProvider.STATE_MESSAGE"
const val ACTION_START = "eu.neilalexander.yggdrasil.PacketTunnelProvider.START" const val ACTION_START = "eu.neilalexander.yggdrasil.PacketTunnelProvider.START"
const val ACTION_STOP = "eu.neilalexander.yggdrasil.PacketTunnelProvider.STOP" const val ACTION_STOP = "eu.neilalexander.yggdrasil.PacketTunnelProvider.STOP"
const val ACTION_TOGGLE = "eu.neilalexander.yggdrasil.PacketTunnelProvider.TOGGLE"
const val ACTION_CONNECT = "eu.neilalexander.yggdrasil.PacketTunnelProvider.CONNECT"
} }
private var yggdrasil = Yggdrasil() private var yggdrasil = Yggdrasil()
private var started = AtomicBoolean() private var started = AtomicBoolean()
private lateinit var config: ConfigurationProxy private lateinit var config: ConfigurationProxy
private lateinit var parcel: ParcelFileDescriptor
private lateinit var readerThread: Thread private var readerThread: Thread? = null
private lateinit var writerThread: Thread private var writerThread: Thread? = null
private lateinit var updateThread: Thread private var updateThread: Thread? = null
private lateinit var readerStream: FileInputStream private var parcel: ParcelFileDescriptor? = null
private lateinit var writerStream: FileOutputStream private var readerStream: FileInputStream? = null
private var writerStream: FileOutputStream? = null
private var multicastLock: WifiManager.MulticastLock? = null
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@ -49,17 +57,40 @@ class PacketTunnelProvider: VpnService() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent == null) { if (intent == null) {
Log.d(TAG, "Intent is null")
return START_NOT_STICKY return START_NOT_STICKY
} }
val preferences = PreferenceManager.getDefaultSharedPreferences(this.baseContext)
val enabled = preferences.getBoolean(PREF_KEY_ENABLED, false)
return when (intent.action ?: ACTION_STOP) { return when (intent.action ?: ACTION_STOP) {
ACTION_START -> {
start(); START_STICKY
}
ACTION_STOP -> { ACTION_STOP -> {
Log.d(TAG, "Stopping...")
stop(); START_NOT_STICKY stop(); START_NOT_STICKY
} }
ACTION_CONNECT -> {
Log.d(TAG, "Connecting...")
if (started.get()) {
connect()
} else {
start()
}
START_STICKY
}
ACTION_TOGGLE -> {
Log.d(TAG, "Toggling...")
if (started.get()) {
stop(); START_NOT_STICKY
} else {
start(); START_STICKY
}
}
else -> { else -> {
stop(); START_NOT_STICKY if (!enabled) {
Log.d(TAG, "Service is disabled")
return START_NOT_STICKY
}
Log.d(TAG, "Starting...")
start(); START_STICKY
} }
} }
} }
@ -69,18 +100,60 @@ class PacketTunnelProvider: VpnService() {
return return
} }
Log.d("PacketTunnelProvider", config.getJSON().toString()) val notification = createServiceNotification(this, State.Enabled)
startForeground(SERVICE_NOTIFICATION_ID, notification)
// Acquire multicast lock
val wifi = applicationContext.getSystemService(WIFI_SERVICE) as WifiManager
multicastLock = wifi.createMulticastLock("Yggdrasil").apply {
setReferenceCounted(false)
acquire()
}
Log.d(TAG, config.getJSON().toString())
yggdrasil.startJSON(config.getJSONByteArray()) yggdrasil.startJSON(config.getJSONByteArray())
val address = yggdrasil.addressString val address = yggdrasil.addressString
var builder = Builder() val builder = Builder()
.addAddress(address, 7) .addAddress(address, 7)
.addRoute("200::", 7) .addRoute("200::", 7)
// We do this to trick the DNS-resolver into thinking that we have "regular" IPv6,
// and therefore we need to resolve AAAA DNS-records.
// See: https://android.googlesource.com/platform/bionic/+/refs/heads/master/libc/dns/net/getaddrinfo.c#1935
// and: https://android.googlesource.com/platform/bionic/+/refs/heads/master/libc/dns/net/getaddrinfo.c#365
// If we don't do this the DNS-resolver just doesn't do DNS-requests with record type AAAA,
// and we can't use DNS with Yggdrasil addresses.
.addRoute("2000::", 128)
.allowFamily(OsConstants.AF_INET)
.allowBypass()
.setBlocking(true) .setBlocking(true)
.setMtu(yggdrasil.mtu.toInt()) .setMtu(yggdrasil.mtu.toInt())
.setSession("Yggdrasil") .setSession("Yggdrasil")
// On Android API 29+ apps can opt-in/out to using metered networks.
// If we don't set metered status of VPN it is considered as metered.
// If we set it to false, then it will inherit this status from underlying network.
// See: https://developer.android.com/reference/android/net/VpnService.Builder#setMetered(boolean)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
builder.setMetered(false)
}
val preferences = PreferenceManager.getDefaultSharedPreferences(this.baseContext)
val serverString = preferences.getString(KEY_DNS_SERVERS, "")
if (serverString!!.isNotEmpty()) {
val servers = serverString.split(",")
if (servers.isNotEmpty()) {
servers.forEach {
Log.i(TAG, "Using DNS server $it")
builder.addDnsServer(it)
}
}
}
if (preferences.getBoolean(KEY_ENABLE_CHROME_FIX, false)) {
builder.addRoute("2001:4860:4860::8888", 128)
}
parcel = builder.establish() parcel = builder.establish()
val parcel = parcel
if (parcel == null || !parcel.fileDescriptor.valid()) { if (parcel == null || !parcel.fileDescriptor.valid()) {
stop() stop()
return return
@ -99,13 +172,8 @@ class PacketTunnelProvider: VpnService() {
updater() updater()
} }
val intent = Intent(RECEIVER_INTENT) var intent = Intent(YGG_STATE_INTENT)
intent.putExtra("type", "state") intent.putExtra("state", STATE_ENABLED)
intent.putExtra("started", true)
intent.putExtra("ip", yggdrasil.addressString)
intent.putExtra("subnet", yggdrasil.subnetString)
intent.putExtra("coords", yggdrasil.coordsString)
intent.putExtra("peers", JSONArray(yggdrasil.peersJSON).length())
LocalBroadcastManager.getInstance(this).sendBroadcast(intent) LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
} }
@ -113,69 +181,167 @@ class PacketTunnelProvider: VpnService() {
if (!started.compareAndSet(true, false)) { if (!started.compareAndSet(true, false)) {
return return
} }
if (readerThread != null) {
readerStream.close()
readerThread.interrupt()
}
if (writerThread != null) {
writerStream.close()
writerThread.interrupt()
}
if (updateThread != null) {
updateThread.interrupt()
}
parcel.close()
yggdrasil.stop()
stopSelf()
val intent = Intent(RECEIVER_INTENT) yggdrasil.stop()
readerStream?.let {
it.close()
readerStream = null
}
writerStream?.let {
it.close()
writerStream = null
}
parcel?.let {
it.close()
parcel = null
}
readerThread?.let {
it.interrupt()
readerThread = null
}
writerThread?.let {
it.interrupt()
writerThread = null
}
updateThread?.let {
it.interrupt()
updateThread = null
}
var intent = Intent(STATE_INTENT)
intent.putExtra("type", "state") intent.putExtra("type", "state")
intent.putExtra("started", false) intent.putExtra("started", false)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent) LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
intent = Intent(YGG_STATE_INTENT)
intent.putExtra("state", STATE_DISABLED)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
stopForeground(true)
stopSelf()
multicastLock?.release()
}
private fun connect() {
if (!started.get()) {
return
}
yggdrasil.retryPeersNow()
} }
private fun updater() { private fun updater() {
updates@ while (!updateThread.isInterrupted) { try {
val intent = Intent(RECEIVER_INTENT) Thread.sleep(500)
intent.putExtra("type", "state") } catch (_: InterruptedException) {
intent.putExtra("started", true) return
intent.putExtra("ip", yggdrasil.addressString)
intent.putExtra("subnet", yggdrasil.subnetString)
intent.putExtra("coords", yggdrasil.coordsString)
intent.putExtra("peers", yggdrasil.peersJSON)
intent.putExtra("dht", yggdrasil.dhtjson)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
try {
Thread.sleep(2000)
} catch (e: java.lang.InterruptedException) {
return
}
} }
var lastStateUpdate = System.currentTimeMillis()
updates@ while (started.get()) {
val treeJSON = yggdrasil.treeJSON
if ((application as GlobalApplication).needUiUpdates()) {
val intent = Intent(STATE_INTENT)
intent.putExtra("type", "state")
intent.putExtra("started", true)
intent.putExtra("ip", yggdrasil.addressString)
intent.putExtra("subnet", yggdrasil.subnetString)
intent.putExtra("pubkey", yggdrasil.publicKeyString)
intent.putExtra("peers", yggdrasil.peersJSON)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
}
val curTime = System.currentTimeMillis()
if (lastStateUpdate + 10000 < curTime) {
val intent = Intent(YGG_STATE_INTENT)
var state = STATE_ENABLED
if (yggdrasil.routingEntries > 0) {
state = STATE_CONNECTED
}
if (treeJSON != null && treeJSON != "null") {
val treeState = JSONArray(treeJSON)
val count = treeState.length()
if (count > 1)
state = STATE_CONNECTED
}
intent.putExtra("state", state)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
lastStateUpdate = curTime
}
if (Thread.currentThread().isInterrupted) {
break@updates
}
if (sleep()) return
}
}
private fun sleep(): Boolean {
try {
Thread.sleep(1000)
} catch (e: InterruptedException) {
return true
}
return false
} }
private fun writer() { private fun writer() {
writes@ while (!writerThread.isInterrupted && writerStream.fd.valid()) { val buf = ByteArray(65535)
writes@ while (started.get()) {
val writerStream = writerStream
val writerThread = writerThread
if (writerThread == null || writerStream == null) {
Log.i(TAG, "Write thread or stream is null")
break@writes
}
if (Thread.currentThread().isInterrupted || !writerStream.fd.valid()) {
Log.i(TAG, "Write thread interrupted or file descriptor is invalid")
break@writes
}
try { try {
val b = yggdrasil.recv() val len = yggdrasil.recvBuffer(buf)
writerStream.write(b) if (len > 0) {
writerStream.write(buf, 0, len.toInt())
}
} catch (e: Exception) { } catch (e: Exception) {
Log.i(TAG, "Error in write: $e")
if (e.toString().contains("ENOBUFS")) {
//TODO Check this by some error code
//More info about this: https://github.com/AdguardTeam/AdguardForAndroid/issues/724
continue
}
break@writes break@writes
} }
} }
stop() writerStream?.let {
it.close()
writerStream = null
}
} }
private fun reader() { private fun reader() {
var b = ByteArray(65535) val b = ByteArray(65535)
reads@ while (!readerThread.isInterrupted && readerStream.fd.valid()) { reads@ while (started.get()) {
val readerStream = readerStream
val readerThread = readerThread
if (readerThread == null || readerStream == null) {
Log.i(TAG, "Read thread or stream is null")
break@reads
}
if (Thread.currentThread().isInterrupted ||!readerStream.fd.valid()) {
Log.i(TAG, "Read thread interrupted or file descriptor is invalid")
break@reads
}
try { try {
val n = readerStream.read(b) val n = readerStream.read(b)
yggdrasil.send(b.sliceArray(0..n)) yggdrasil.sendBuffer(b, n.toLong())
} catch (e: Exception) { } catch (e: Exception) {
Log.i(TAG, "Error in sendBuffer: $e")
break@reads break@reads
} }
} }
stop() readerStream?.let {
it.close()
readerStream = null
}
} }
} }

View file

@ -1,53 +0,0 @@
package eu.neilalexander.yggdrasil
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import org.json.JSONArray
object PacketTunnelState: BroadcastReceiver() {
var dhtState: JSONArray? = null
private set
var peersState: JSONArray? = null
private set
const val RECEIVER_INTENT = "eu.neilalexander.yggdrasil.PacketTunnelState.MESSAGE"
fun peerCount(): Int {
if (peersState == null) {
return 0
}
return peersState!!.length()
}
fun dhtCount(): Int {
if (dhtState == null) {
return 0
}
return dhtState!!.length()
}
override fun onReceive(context: Context?, intent: Intent) {
when (intent.getStringExtra("type")) {
"state" -> {
var dht = intent.getStringExtra("dht")
var peers = intent.getStringExtra("peers")
if (dht == null || dht == "null") {
dht = "[]"
}
if (peers == null || peers == "null") {
peers = "[]"
}
peersState = JSONArray(peers)
dhtState = JSONArray(dht)
intent.action = RECEIVER_INTENT
LocalBroadcastManager.getInstance(context!!).sendBroadcast(intent)
}
}
}
}

View file

@ -1,29 +1,43 @@
package eu.neilalexander.yggdrasil package eu.neilalexander.yggdrasil
import android.app.AlertDialog import android.app.AlertDialog
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.Intent
import androidx.appcompat.app.AppCompatActivity import android.content.IntentFilter
import android.os.Bundle import android.os.Bundle
import android.text.Layout import android.text.method.LinkMovementMethod
import android.util.AttributeSet
import android.util.Log import android.util.Log
import android.view.ContextThemeWrapper
import android.view.KeyEvent
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.widget.* import android.widget.EditText
import android.widget.ImageButton
import android.widget.Switch
import android.widget.TableLayout
import android.widget.TableRow
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.doOnTextChanged
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputEditText
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject
class PeersActivity : AppCompatActivity() { class PeersActivity : AppCompatActivity() {
private var state = PacketTunnelState
private lateinit var config: ConfigurationProxy private lateinit var config: ConfigurationProxy
private lateinit var inflater: LayoutInflater private lateinit var inflater: LayoutInflater
private lateinit var peers: Array<JSONObject>
private lateinit var connectedTableLayout: TableLayout private lateinit var connectedTableLayout: TableLayout
private lateinit var connectedTableLabel: TextView private lateinit var connectedTableLabel: TextView
private lateinit var configuredTableLayout: TableLayout private lateinit var configuredTableLayout: TableLayout
private lateinit var configuredTableLabel: TextView private lateinit var configuredTableLabel: TextView
private lateinit var multicastSwitch: Switch private lateinit var multicastListenSwitch: Switch
private lateinit var multicastBeaconSwitch: Switch
private lateinit var passwordEdit: EditText
private lateinit var addPeerButton: ImageButton private lateinit var addPeerButton: ImageButton
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -32,6 +46,7 @@ class PeersActivity : AppCompatActivity() {
config = ConfigurationProxy(applicationContext) config = ConfigurationProxy(applicationContext)
inflater = LayoutInflater.from(this) inflater = LayoutInflater.from(this)
peers = emptyArray()
connectedTableLayout = findViewById(R.id.connectedPeersTableLayout) connectedTableLayout = findViewById(R.id.connectedPeersTableLayout)
connectedTableLabel = findViewById(R.id.connectedPeersLabel) connectedTableLabel = findViewById(R.id.connectedPeersLabel)
@ -39,45 +54,68 @@ class PeersActivity : AppCompatActivity() {
configuredTableLayout = findViewById(R.id.configuredPeersTableLayout) configuredTableLayout = findViewById(R.id.configuredPeersTableLayout)
configuredTableLabel = findViewById(R.id.configuredPeersLabel) configuredTableLabel = findViewById(R.id.configuredPeersLabel)
multicastSwitch = findViewById(R.id.enableMulticastSwitch) val discoveryLink = findViewById<TextView>(R.id.peers_discovery_link)
multicastSwitch.setOnCheckedChangeListener { button, _ -> discoveryLink.movementMethod = LinkMovementMethod.getInstance()
when (button.isChecked) {
true -> { multicastListenSwitch = findViewById(R.id.enableMulticastListen)
config.updateJSON { json -> multicastListenSwitch.setOnCheckedChangeListener { button, _ ->
json.put("MulticastInterfaces", JSONArray("[\"lo\", \".*\"]")) config.multicastListen = button.isChecked
} }
} multicastBeaconSwitch = findViewById(R.id.enableMulticastBeacon)
false -> { multicastBeaconSwitch.setOnCheckedChangeListener { button, _ ->
config.updateJSON { json -> config.multicastBeacon = button.isChecked
json.put("MulticastInterfaces", JSONArray("[\"lo\"]")) }
} multicastListenSwitch.isChecked = config.multicastListen
multicastBeaconSwitch.isChecked = config.multicastBeacon
val multicastBeaconPanel = findViewById<TableRow>(R.id.enableMulticastBeaconPanel)
multicastBeaconPanel.setOnClickListener {
multicastBeaconSwitch.toggle()
}
val multicastListenPanel = findViewById<TableRow>(R.id.enableMulticastListenPanel)
multicastListenPanel.setOnClickListener {
multicastListenSwitch.toggle()
}
passwordEdit = findViewById(R.id.passwordEdit)
passwordEdit.setText(config.multicastPassword)
passwordEdit.doOnTextChanged { text, _, _, _ ->
config.multicastPassword = text.toString()
}
passwordEdit.setOnKeyListener { _, keyCode, _ ->
(keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER)
}
findViewById<View>(R.id.passwordTableRow).setOnKeyListener { _, keyCode, event ->
Log.i("Key", keyCode.toString())
if (event.action == KeyEvent.ACTION_DOWN) {
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) {
passwordEdit.requestFocus()
true
} else {
false
} }
} else {
false
} }
} }
var multicastInterfaceFound = false
val multicastInterfaces = config.getJSON().getJSONArray("MulticastInterfaces")
(0 until multicastInterfaces.length()).forEach {
if (multicastInterfaces[it] == ".*") {
multicastInterfaceFound = true
}
}
multicastSwitch.isChecked = multicastInterfaceFound
addPeerButton = findViewById(R.id.addPeerButton) addPeerButton = findViewById(R.id.addPeerButton)
addPeerButton.setOnClickListener { addPeerButton.setOnClickListener {
var view = inflater.inflate(R.layout.dialog_addpeer, null) val view = inflater.inflate(R.layout.dialog_addpeer, null)
var input = view.findViewById<TextInputEditText>(R.id.addPeerInput) val input = view.findViewById<TextInputEditText>(R.id.addPeerInput)
val builder: AlertDialog.Builder = AlertDialog.Builder(this) val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.YggdrasilDialogs))
builder.setTitle("Add Configured Peer") builder.setTitle(getString(R.string.peers_add_peer))
builder.setView(view) builder.setView(view)
builder.setPositiveButton("Add") { dialog, _ -> builder.setPositiveButton(getString(R.string.peers_add)) { dialog, _ ->
config.updateJSON { json -> config.updateJSON { json ->
json.getJSONArray("Peers").put(input.text) json.getJSONArray("Peers").put(input.text.toString().trim())
} }
dialog.dismiss() dialog.dismiss()
updateConfiguredPeers() updateConfiguredPeers()
} }
builder.setNegativeButton("Cancel") { dialog, _ -> builder.setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
dialog.cancel() dialog.cancel()
} }
builder.show() builder.show()
@ -86,41 +124,51 @@ class PeersActivity : AppCompatActivity() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
LocalBroadcastManager.getInstance(this).registerReceiver(
receiver, IntentFilter(PacketTunnelProvider.STATE_INTENT)
)
(application as GlobalApplication).subscribe()
updateConfiguredPeers() updateConfiguredPeers()
updateConnectedPeers() updateConnectedPeers()
} }
override fun onPause() {
super.onPause()
(application as GlobalApplication).unsubscribe()
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
}
private fun updateConfiguredPeers() { private fun updateConfiguredPeers() {
val peers = config.getJSON().getJSONArray("Peers") val peers = config.getJSON().getJSONArray("Peers")
when (peers.length()) { when (peers.length()) {
0 -> { 0 -> {
configuredTableLayout.visibility = View.GONE configuredTableLayout.visibility = View.GONE
configuredTableLabel.text = "No peers currently configured" configuredTableLabel.text = getString(R.string.peers_no_configured_title)
} }
else -> { else -> {
configuredTableLayout.visibility = View.VISIBLE configuredTableLayout.visibility = View.VISIBLE
configuredTableLabel.text = "Configured Peers" configuredTableLabel.text = getString(R.string.peers_configured_title)
configuredTableLayout.removeAllViewsInLayout() configuredTableLayout.removeAllViewsInLayout()
for (i in 0 until peers.length()) { for (i in 0 until peers.length()) {
val peer = peers[i].toString() val peer = peers[i].toString()
var view = inflater.inflate(R.layout.peers_configured, null) val view = inflater.inflate(R.layout.peers_configured, null)
view.findViewById<TextView>(R.id.addressValue).text = peer view.findViewById<TextView>(R.id.addressValue).text = peer
view.findViewById<ImageButton>(R.id.deletePeerButton).tag = i view.findViewById<ImageButton>(R.id.deletePeerButton).tag = i
view.findViewById<ImageButton>(R.id.deletePeerButton).setOnClickListener { button -> view.findViewById<ImageButton>(R.id.deletePeerButton).setOnClickListener { button ->
val builder: AlertDialog.Builder = AlertDialog.Builder(this) val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.YggdrasilDialogs))
builder.setTitle("Remove ${peer}?") builder.setTitle(getString(R.string.peers_remove_title, peer))
builder.setPositiveButton("Remove") { dialog, _ -> builder.setPositiveButton(getString(R.string.peers_remove)) { dialog, _ ->
config.updateJSON { json -> config.updateJSON { json ->
json.getJSONArray("Peers").remove(button.tag as Int) json.getJSONArray("Peers").remove(button.tag as Int)
} }
dialog.dismiss() dialog.dismiss()
updateConfiguredPeers() updateConfiguredPeers()
} }
builder.setNegativeButton("Cancel") { dialog, _ -> builder.setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
dialog.cancel() dialog.cancel()
} }
builder.show() builder.show()
@ -132,25 +180,52 @@ class PeersActivity : AppCompatActivity() {
} }
private fun updateConnectedPeers() { private fun updateConnectedPeers() {
val peers = state.peersState ?: JSONArray("[]") when (peers.size) {
when (peers.length()) {
0 -> { 0 -> {
connectedTableLayout.visibility = View.GONE connectedTableLayout.visibility = View.GONE
connectedTableLabel.text = "No peers currently connected" connectedTableLabel.text = getString(R.string.peers_no_connected_title)
} }
else -> { else -> {
connectedTableLayout.visibility = View.VISIBLE var connected = false
connectedTableLabel.text = "Connected Peers"
connectedTableLayout.removeAllViewsInLayout() connectedTableLayout.removeAllViewsInLayout()
for (i in 0 until peers.length()) { for (peer in peers) {
val peer = peers.getJSONObject(i) val view = inflater.inflate(R.layout.peers_connected, null)
var view = inflater.inflate(R.layout.peers_connected, null)
val ip = peer.getString("IP") val ip = peer.getString("IP")
view.findViewById<TextView>(R.id.addressLabel).text = ip // Only connected peers have IPs
view.findViewById<TextView>(R.id.detailsLabel).text = peer.getString("Remote") if (ip.isNotEmpty()) {
connectedTableLayout.addView(view) view.findViewById<TextView>(R.id.addressLabel).text = ip
view.findViewById<TextView>(R.id.detailsLabel).text = peer.getString("URI")
connectedTableLayout.addView(view)
connected = true
}
}
if (connected) {
connectedTableLayout.visibility = View.VISIBLE
connectedTableLabel.text = getString(R.string.peers_connected_title)
} else {
connectedTableLayout.visibility = View.GONE
connectedTableLabel.text = getString(R.string.peers_no_connected_title)
}
}
}
}
private val receiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
when (intent.getStringExtra("type")) {
"state" -> {
if (intent.hasExtra("peers")) {
val peers1 = intent.getStringExtra("peers")
//Log.i("PeersActivity", "Peers json: $peers1")
val peersArray = JSONArray(peers1 ?: "[]")
val array = Array(peersArray.length()) { i ->
peersArray.getJSONObject(i)
}
array.sortWith(compareBy { it.getString("IP") })
peers = array
updateConnectedPeers()
}
} }
} }
} }

View file

@ -1,11 +1,162 @@
package eu.neilalexander.yggdrasil package eu.neilalexander.yggdrasil
import android.app.AlertDialog
import android.content.BroadcastReceiver
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.ContextThemeWrapper
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.widget.*
import androidx.appcompat.widget.LinearLayoutCompat
import androidx.core.widget.doOnTextChanged
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import org.json.JSONObject
class SettingsActivity : AppCompatActivity() { class SettingsActivity : AppCompatActivity() {
private lateinit var config: ConfigurationProxy
private lateinit var inflater: LayoutInflater
private lateinit var deviceNameEntry: EditText
private lateinit var publicKeyLabel: TextView
private lateinit var resetConfigurationRow: LinearLayoutCompat
private var publicKeyReset = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings) setContentView(R.layout.activity_settings)
config = ConfigurationProxy(applicationContext)
inflater = LayoutInflater.from(this)
deviceNameEntry = findViewById(R.id.deviceNameEntry)
publicKeyLabel = findViewById(R.id.publicKeyLabel)
resetConfigurationRow = findViewById(R.id.resetConfigurationRow)
deviceNameEntry.doOnTextChanged { text, _, _, _ ->
config.updateJSON { cfg ->
val nodeInfo = cfg.optJSONObject("NodeInfo")
if (nodeInfo == null) {
cfg.put("NodeInfo", JSONObject("{}"))
}
cfg.getJSONObject("NodeInfo").put("name", text)
}
}
deviceNameEntry.setOnKeyListener { view, keyCode, event ->
(keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER)
}
findViewById<View>(R.id.deviceNameTableRow).setOnKeyListener { view, keyCode, event ->
Log.i("Key", keyCode.toString())
if (event.action == KeyEvent.ACTION_DOWN) {
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) {
deviceNameEntry.requestFocus()
true
} else {
false
}
} else {
false
}
}
resetConfigurationRow.setOnClickListener {
val view = inflater.inflate(R.layout.dialog_resetconfig, null)
val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.YggdrasilDialogs))
builder.setTitle(getString(R.string.settings_warning_title))
builder.setView(view)
builder.setPositiveButton(getString(R.string.settings_reset)) { dialog, _ ->
config.resetJSON()
updateView()
dialog.dismiss()
}
builder.setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
dialog.cancel()
}
builder.show()
}
findViewById<View>(R.id.resetKeysRow).setOnClickListener {
config.resetKeys()
publicKeyReset = true
updateView()
}
findViewById<View>(R.id.setKeysRow).setOnClickListener {
val view = inflater.inflate(R.layout.dialog_set_keys, null)
val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.YggdrasilDialogs))
val privateKey = view.findViewById<EditText>(R.id.private_key)
builder.setTitle(getString(R.string.set_keys))
builder.setView(view)
builder.setPositiveButton(getString(R.string.save)) { dialog, _ ->
config.setKeys(privateKey.text.toString())
updateView()
dialog.dismiss()
}
builder.setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
dialog.cancel()
}
builder.show()
}
publicKeyLabel.setOnLongClickListener {
val clipboard: ClipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("public key", publicKeyLabel.text)
clipboard.setPrimaryClip(clip)
Toast.makeText(applicationContext,R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
true
}
updateView()
} }
}
private fun updateView() {
val json = config.getJSON()
val nodeinfo = json.optJSONObject("NodeInfo")
if (nodeinfo != null) {
deviceNameEntry.setText(nodeinfo.getString("name"), TextView.BufferType.EDITABLE)
} else {
deviceNameEntry.setText("", TextView.BufferType.EDITABLE)
}
var key = json.optString("PrivateKey")
if (key.isNotEmpty()) {
key = key.substring(key.length / 2)
}
publicKeyLabel.text = key
}
override fun onResume() {
super.onResume()
LocalBroadcastManager.getInstance(this).registerReceiver(
receiver, IntentFilter(PacketTunnelProvider.STATE_INTENT)
)
(application as GlobalApplication).subscribe()
}
override fun onPause() {
super.onPause()
(application as GlobalApplication).unsubscribe()
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
}
// To be able to get public key from running Yggdrasil we use this receiver, as we don't have this field in config
private val receiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
if (intent.hasExtra("pubkey") && !publicKeyReset) {
val tree = intent.getStringExtra("pubkey")
if (tree != null && tree != "null") {
publicKeyLabel.text = intent.getStringExtra("pubkey")
}
}
}
}
}

View file

@ -0,0 +1,17 @@
package eu.neilalexander.yggdrasil
import android.app.Activity
import android.content.Intent
import android.os.Bundle
class TileServiceActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Just starting MainActivity
val intent = Intent(this, MainActivity::class.java)
startService(intent)
finish()
}
}

View file

@ -0,0 +1,53 @@
package eu.neilalexander.yggdrasil
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.localbroadcastmanager.content.LocalBroadcastManager
const val STATE_ENABLED = "enabled"
const val STATE_DISABLED = "disabled"
const val STATE_CONNECTED = "connected"
const val STATE_RECONNECTING = "reconnecting"
class YggStateReceiver(var receiver: StateReceiver): BroadcastReceiver() {
companion object {
const val YGG_STATE_INTENT = "eu.neilalexander.yggdrasil.YggStateReceiver.STATE"
}
override fun onReceive(context: Context?, intent: Intent?) {
if (context == null) return
val state = when (intent?.getStringExtra("state")) {
STATE_ENABLED -> State.Enabled
STATE_DISABLED -> State.Disabled
STATE_CONNECTED -> State.Connected
STATE_RECONNECTING -> State.Reconnecting
else -> State.Unknown
}
receiver.onStateChange(state)
}
fun register(context: Context) {
LocalBroadcastManager.getInstance(context).registerReceiver(
this, IntentFilter(YGG_STATE_INTENT)
)
}
fun unregister(context: Context) {
LocalBroadcastManager.getInstance(context).unregisterReceiver(this)
}
interface StateReceiver {
fun onStateChange(state: State)
}
}
/**
* A class-supporter with an Yggdrasil state
*/
enum class State {
Unknown, Disabled, Enabled, Connected, Reconnecting;
}

View file

@ -0,0 +1,113 @@
package eu.neilalexander.yggdrasil
import android.content.Intent
import android.graphics.drawable.Icon
import android.os.Build
import android.os.IBinder
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.content.edit
import androidx.preference.PreferenceManager
private const val TAG = "TileService"
@RequiresApi(Build.VERSION_CODES.N)
class YggTileService: TileService(), YggStateReceiver.StateReceiver {
private lateinit var receiver: YggStateReceiver
override fun onCreate() {
super.onCreate()
receiver = YggStateReceiver(this)
}
/**
* We need to override the method onBind to avoid crashes that were detected on Android 8
*
* The possible reason of crashes is described here:
* https://github.com/aosp-mirror/platform_frameworks_base/commit/ee68fd889c2dfcd895b8e73fc39d7b97826dc3d8
*/
override fun onBind(intent: Intent?): IBinder? {
return try {
super.onBind(intent)
} catch (th: Throwable) {
null
}
}
override fun onTileAdded() {
super.onTileAdded()
updateTileState((application as GlobalApplication).getCurrentState())
}
override fun onTileRemoved() {
super.onTileRemoved()
updateTileState((application as GlobalApplication).getCurrentState())
}
override fun onStartListening() {
super.onStartListening()
receiver.register(this)
updateTileState((application as GlobalApplication).getCurrentState())
}
override fun onStopListening() {
super.onStopListening()
receiver.unregister(this)
}
override fun onDestroy() {
super.onDestroy()
receiver.unregister(this)
}
override fun onClick() {
super.onClick()
// Saving new state
val preferences = PreferenceManager.getDefaultSharedPreferences(this.baseContext)
val enabled = preferences.getBoolean(PREF_KEY_ENABLED, false)
preferences.edit(commit = true) { putBoolean(PREF_KEY_ENABLED, !enabled) }
// Starting or stopping VPN service
val intent = Intent(this, PacketTunnelProvider::class.java)
intent.action = PacketTunnelProvider.ACTION_TOGGLE
startService(intent)
}
private fun updateTileState(state: State) {
val tile = qsTile ?: return
val oldState = tile.state
val preferences = PreferenceManager.getDefaultSharedPreferences(this.baseContext)
val enabled = preferences.getBoolean(PREF_KEY_ENABLED, false)
tile.state = when (enabled) {
false -> Tile.STATE_INACTIVE
true -> Tile.STATE_ACTIVE
}
var changed = oldState != tile.state
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val oldText = tile.subtitle
tile.subtitle = when (state) {
State.Enabled -> getText(R.string.tile_enabled)
State.Connected -> getText(R.string.tile_connected)
else -> getText(R.string.tile_disabled)
}
changed = changed || (oldText != tile.subtitle)
}
// Update tile if changed state
if (changed) {
Log.i(TAG, "Updating tile, old state: $oldState, new state: ${tile.state}")
/*
Force set the icon in the tile, because there is a problem on icon tint in the Android Oreo.
Issue: https://github.com/AdguardTeam/AdguardForAndroid/issues/1996
*/
tile.icon = Icon.createWithResource(applicationContext, R.drawable.ic_tile_icon)
tile.updateTile()
}
}
override fun onStateChange(state: State) {
updateTileState(state)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -1,30 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -1,170 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View file

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="480dp"
android:height="480dp"
android:viewportWidth="480"
android:viewportHeight="480">
<path
android:pathData="m58.51,474.95c1.37,-5.62 17.69,-45.43 32.05,-78.2c14.7,-33.54 14.42,-32.23 10.32,-49.01c-5.11,-20.95 -4.8,-55.06 0.67,-73.17c19.73,-65.38 70.97,-109.69 182.24,-157.59c36.24,-15.6 56.14,-25.61 71.24,-35.83c26.61,-18.01 54.3,-49.27 63.15,-71.29c1.87,-4.65 3.96,-8.4 4.66,-8.35c2.18,0.16 1.1,66.01 -1.46,88.95c-15.82,142.09 -64.01,234.52 -143.35,274.93c-45.79,23.32 -117.97,31.97 -151.59,18.15c-4.75,-1.95 -9.76,-3.55 -11.12,-3.55c-5.12,0 -23.48,49.02 -28.75,76.76c-1.64,8.61 -4.12,20.42 -4.48,22.23c-8.2,-0.06 -0.53,-0.02 -12.19,-0.02l-12.36,0z"
android:strokeLineJoin="miter"
android:strokeWidth="1.49"
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:strokeColor="#00000000"
android:strokeLineCap="butt"/>
</vector>

View file

@ -0,0 +1,241 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".DnsActivity">
<FrameLayout
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:dividerPadding="4pt"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/pageTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8pt"
android:text="@string/dns_activity_title"
android:textColor="?attr/textDefault"
android:textSize="24sp"
android:textStyle="bold" />
<Space
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<ImageButton
android:id="@+id/addServerButton"
android:layout_width="16pt"
android:layout_height="16pt"
android:layout_marginTop="6pt"
android:layout_marginRight="8pt"
android:background="@android:color/transparent"
app:srcCompat="@drawable/ic_baseline_add_circle_24" />
</LinearLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="@+id/configuredDnsLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginTop="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="2pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text=""
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<TableLayout
android:id="@+id/configuredDnsTableLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8pt"
android:layout_marginLeft="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:background="@drawable/rounded"
android:divider="#46878787"
android:dividerPadding="4pt"
android:paddingLeft="4pt"
android:paddingTop="2pt"
android:paddingRight="4pt"
android:paddingBottom="2pt"
android:showDividers="middle" />
<TextView
android:id="@+id/configuredDnsHint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginTop="4pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="4pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="@string/dns_configured_servers_hint"
android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<TextView
android:id="@+id/usableDnsLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginTop="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="2pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="@string/dns_usable_servers"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<TableLayout
android:id="@+id/usableDnsTableLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8pt"
android:layout_marginLeft="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:background="@drawable/rounded"
android:divider="#46878787"
android:dividerPadding="4pt"
android:paddingLeft="4pt"
android:paddingTop="2pt"
android:paddingRight="4pt"
android:paddingBottom="2pt"
android:showDividers="middle" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginTop="4pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="4pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="@string/dns_usable_servers_hint"
android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<Space
android:layout_width="match_parent"
android:layout_height="32px" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="2pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="@string/dns_fixes"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<TableLayout
android:id="@+id/fixesTableLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8pt"
android:layout_marginLeft="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:background="@drawable/rounded"
android:divider="#46878787"
android:dividerPadding="4pt"
android:paddingLeft="4pt"
android:paddingTop="2pt"
android:paddingRight="4pt"
android:paddingBottom="2pt"
android:showDividers="middle">
<TableRow
android:id="@+id/enableChromeFixPanel"
style="@style/SelectableSwitchItemStyle">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dns_fix_chrome_based_browsers"
android:textColor="?attr/textDefault" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="2" />
<Switch
android:id="@+id/enableChromeFix"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</TableRow>
</TableLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginTop="4pt"
android:layout_marginBottom="4pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="@string/dns_fix_chrome_based_browsers_hint"
android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
</LinearLayout>
</ScrollView>
</LinearLayout>
</FrameLayout>
</LinearLayout>

View file

@ -22,411 +22,382 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8pt" android:layout_margin="8pt"
android:text="Yggdrasil" android:text="@string/app_name"
android:textColor="?attr/textDefault" android:textColor="?attr/textDefault"
android:textSize="24sp" android:textSize="24sp"
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <ScrollView
android:id="@+id/statusSectionLabel"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent">
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginTop="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="2pt"
android:alpha="0.7"
android:text="Status"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<TableLayout <LinearLayout
android:id="@+id/connectedPeersTableLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8pt"
android:layout_marginLeft="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="8pt"
android:background="@drawable/rounded"
android:divider="#46878787"
android:dividerPadding="4pt"
android:paddingLeft="4pt"
android:paddingTop="2pt"
android:paddingRight="4pt"
android:paddingBottom="2pt"
android:showDividers="middle">
<TableRow
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="4pt" android:orientation="vertical" >
android:layout_marginLeft="4pt"
android:layout_marginTop="6pt"
android:layout_marginEnd="4pt"
android:layout_marginRight="4pt"
android:layout_marginBottom="6pt">
<TextView <TextView
android:id="@+id/enableYggdrasilLabel" android:id="@+id/statusSectionLabel"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Enable Yggdrasil" android:layout_marginStart="16pt"
android:textColor="?attr/textDefault" android:layout_marginLeft="16pt"
android:textSize="14sp" /> android:layout_marginTop="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="2pt"
android:alpha="0.7"
android:text="@string/main_status"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/connectedPeersTableLayout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8pt"
android:layout_marginLeft="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="8pt"
android:background="@drawable/rounded"
android:divider="#46878787"
android:dividerPadding="4pt"
android:paddingLeft="4pt"
android:paddingTop="2pt"
android:paddingRight="4pt"
android:paddingBottom="2pt"
android:showDividers="middle">
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/enableYggdrasilPanel"
style="@style/SelectableSwitchItemStyle">
<TextView
android:id="@+id/enableYggdrasilLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_enable_yggdrasil"
android:textColor="?attr/textDefault"
android:textSize="14sp" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="2" />
<Switch
android:id="@+id/enableYggdrasil"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat style="@style/SelectableItemStyle">
<TextView
android:id="@+id/yggdrasilStatusLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="2dp"
android:paddingBottom="2dp"
android:text="@string/main_disabled"
android:textSize="14sp"
android:textStyle="bold" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
<Space <Space
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="32px" />
android:layout_weight="2" />
<Switch
android:id="@+id/enableMulticastSwitch"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4pt"
android:layout_marginLeft="4pt"
android:layout_marginTop="6pt"
android:layout_marginEnd="4pt"
android:layout_marginRight="4pt"
android:layout_marginBottom="6pt">
<TextView <TextView
android:id="@+id/yggdrasilStatusLabel" android:id="@+id/networkInfoSectionLabel"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Not enabled" android:layout_marginStart="16pt"
android:textSize="14sp" android:layout_marginLeft="16pt"
android:textStyle="bold" /> android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="2pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="@string/main_network_info"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
</TableRow> <androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/configuredPeersTableLayout"
</TableLayout> android:orientation="vertical"
android:layout_width="match_parent"
<Space
android:layout_width="match_parent"
android:layout_height="32px" />
<TextView
android:id="@+id/statisticsSectionLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="2pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="Statistics"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<TableLayout
android:id="@+id/configuredPeersTableLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8pt"
android:layout_marginLeft="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="8pt"
android:background="@drawable/rounded"
android:divider="#46878787"
android:dividerPadding="4pt"
android:paddingLeft="4pt"
android:paddingTop="2pt"
android:paddingRight="4pt"
android:paddingBottom="2pt"
android:showDividers="middle">
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="4pt"
android:layout_marginLeft="4pt"
android:layout_marginTop="6pt"
android:layout_marginEnd="4pt"
android:layout_marginRight="4pt"
android:layout_marginBottom="6pt">
<TextView
android:id="@+id/ipAddressLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="0" android:layout_marginStart="8pt"
android:text="IP" android:layout_marginLeft="8pt"
android:textColor="?attr/textDefault" /> android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="8pt"
android:background="@drawable/rounded"
android:divider="#46878787"
android:dividerPadding="4pt"
android:paddingLeft="4pt"
android:paddingTop="2pt"
android:paddingRight="4pt"
android:paddingBottom="2pt"
android:showDividers="middle">
<TextView <androidx.appcompat.widget.LinearLayoutCompat style="@style/SelectableItemStyle">
android:id="@+id/ipAddressValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:alpha="0.5"
android:ellipsize="none"
android:scrollHorizontally="true"
android:selectAllOnFocus="true"
android:singleLine="true"
android:text="N/A"
android:textAlignment="viewEnd"
android:textIsSelectable="true"
android:textSize="14sp" />
</TableRow>
<TableRow <TextView
android:layout_width="match_parent" android:id="@+id/ipAddressLabel"
android:layout_height="match_parent" android:layout_width="wrap_content"
android:layout_marginStart="4pt" android:layout_height="wrap_content"
android:layout_marginLeft="4pt" android:layout_weight="0"
android:layout_marginTop="6pt" android:text="@string/main_ip"
android:layout_marginEnd="4pt" android:textColor="?attr/textDefault" />
android:layout_marginRight="4pt"
android:layout_marginBottom="6pt">
<TextView <TextView
android:id="@+id/subnetLabel" android:id="@+id/ipAddressValue"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="0" android:layout_weight="1"
android:text="Subnet" android:alpha="0.5"
android:textColor="?attr/textDefault" /> android:ellipsize="none"
android:scrollHorizontally="true"
android:selectAllOnFocus="true"
android:singleLine="true"
android:text="@string/main_not_available"
android:textAlignment="viewEnd"
android:textIsSelectable="true"
android:textSize="14sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<TextView <androidx.appcompat.widget.LinearLayoutCompat style="@style/SelectableItemStyle">
android:id="@+id/subnetValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:alpha="0.5"
android:ellipsize="none"
android:scrollHorizontally="true"
android:selectAllOnFocus="true"
android:singleLine="true"
android:text="N/A"
android:textAlignment="viewEnd"
android:textIsSelectable="true"
android:textSize="14sp" />
</TableRow> <TextView
android:id="@+id/subnetLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:text="@string/main_subnet"
android:textColor="?attr/textDefault" />
<TableRow <TextView
android:layout_width="match_parent" android:id="@+id/subnetValue"
android:layout_height="match_parent" android:layout_width="wrap_content"
android:layout_marginStart="4pt" android:layout_height="wrap_content"
android:layout_marginLeft="4pt" android:layout_weight="1"
android:layout_marginTop="6pt" android:alpha="0.5"
android:layout_marginEnd="4pt" android:ellipsize="none"
android:layout_marginRight="4pt" android:scrollHorizontally="true"
android:layout_marginBottom="6pt"> android:selectAllOnFocus="true"
android:singleLine="true"
android:text="@string/main_not_available"
android:textAlignment="viewEnd"
android:textIsSelectable="true"
android:textSize="14sp" />
<TextView </androidx.appcompat.widget.LinearLayoutCompat>
android:id="@+id/coordinatesLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:text="Coordinates"
android:textColor="?attr/textDefault" />
<TextView </androidx.appcompat.widget.LinearLayoutCompat>
android:id="@+id/coordinatesValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:alpha="0.5"
android:ellipsize="end"
android:maxLines="1"
android:scrollHorizontally="false"
android:selectAllOnFocus="true"
android:singleLine="true"
android:text="[]"
android:textAlignment="viewEnd"
android:textIsSelectable="true" />
</TableRow>
</TableLayout>
<Space
android:layout_width="match_parent"
android:layout_height="32px" />
<TextView
android:id="@+id/configurationSectionLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="2pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="Configuration"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<TableLayout
android:id="@+id/configurationTableLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8pt"
android:layout_marginLeft="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:background="@drawable/rounded"
android:clickable="true"
android:divider="#46878787"
android:dividerPadding="4pt"
android:paddingLeft="4pt"
android:paddingTop="2pt"
android:paddingRight="4pt"
android:paddingBottom="2pt"
android:showDividers="middle">
<TableRow
android:id="@+id/peersTableRow"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:paddingStart="4pt"
android:paddingTop="6pt"
android:paddingEnd="4pt"
android:paddingBottom="6pt">
<TextView
android:id="@+id/multicastLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Peers"
android:textColor="?attr/textDefault" />
<Space <Space
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="32px" />
android:layout_weight="2" />
<TextView <TextView
android:id="@+id/peersValue" android:id="@+id/configurationSectionLabel"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:alpha="0.5" android:layout_marginStart="16pt"
android:text="No peers" android:layout_marginLeft="16pt"
android:textAlignment="textEnd" /> android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="2pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="@string/main_configuration"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<ImageView <androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/peersChevron" android:id="@+id/configurationTableLayout"
android:layout_width="wrap_content" android:orientation="vertical"
android:layout_height="match_parent" android:layout_width="match_parent"
android:layout_marginLeft="2pt" android:layout_height="wrap_content"
android:alpha="0.4" android:layout_marginStart="8pt"
android:cropToPadding="false" android:layout_marginLeft="8pt"
android:scaleType="fitEnd" android:layout_marginEnd="8pt"
android:scaleX="1.2" android:layout_marginRight="8pt"
android:scaleY="1.2" android:background="@drawable/rounded"
app:srcCompat="@drawable/ic_baseline_chevron_right_24" /> android:divider="#46878787"
</TableRow> android:dividerPadding="4pt"
android:paddingLeft="4pt"
android:paddingTop="2pt"
android:paddingRight="4pt"
android:paddingBottom="2pt"
android:showDividers="middle">
<TableRow <androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/settingsTableRow" android:id="@+id/peersTableRow"
android:layout_width="match_parent" style="@style/SelectableItemStyle">
android:layout_height="match_parent"
android:background="?android:attr/selectableItemBackground" <TextView
android:paddingStart="4pt" android:id="@+id/multicastLabel"
android:paddingTop="6pt" android:layout_width="wrap_content"
android:paddingEnd="4pt" android:layout_height="wrap_content"
android:paddingBottom="6pt" android:text="@string/main_peers"
android:visibility="gone"> android:textColor="?attr/textDefault" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="2" />
<TextView
android:id="@+id/peersValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.5"
android:text="@string/main_no_peers"
android:textAlignment="textEnd" />
<ImageView
android:id="@+id/peersChevron"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="2pt"
android:alpha="0.4"
android:cropToPadding="false"
android:scaleType="fitEnd"
android:scaleX="1.2"
android:scaleY="1.2"
app:srcCompat="@drawable/ic_baseline_chevron_right_24" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/dnsTableRow"
style="@style/SelectableItemStyle">
<TextView
android:id="@+id/dnsLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_dns_servers"
android:textColor="?attr/textDefault" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="2" />
<TextView
android:id="@+id/dnsValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.5"
android:text="@string/dns_no_servers"
android:textAlignment="textEnd" />
<ImageView
android:id="@+id/dnsChevron"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="2pt"
android:alpha="0.4"
android:cropToPadding="false"
android:scaleType="fitEnd"
android:scaleX="1.2"
android:scaleY="1.2"
app:srcCompat="@drawable/ic_baseline_chevron_right_24" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/settingsTableRow"
style="@style/SelectableItemStyle">
<TextView
android:id="@+id/settingsLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_settings"
android:textColor="?attr/textDefault" />
<Space
android:layout_width="320dp"
android:layout_height="match_parent"
android:layout_weight="2" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/settingsChevron"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="2pt"
android:alpha="0.4"
android:scaleType="fitEnd"
android:scaleX="1.2"
android:scaleY="1.2"
app:srcCompat="@drawable/ic_baseline_chevron_right_24" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/versionTableRow"
style="@style/SelectableItemStyle">
<TextView
android:id="@+id/versionLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_version"
android:textColor="?attr/textDefault" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="2" />
<TextView
android:id="@+id/versionValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.5"
android:text="@string/main_unknown"
android:textAlignment="textEnd" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
<TextView <TextView
android:id="@+id/settingsLabel" android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Settings" android:layout_marginStart="16pt"
android:textColor="?attr/textDefault" /> android:layout_marginLeft="16pt"
android:layout_marginTop="4pt"
android:layout_marginBottom="4pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="@string/main_bottom_warning"
android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<Space </LinearLayout>
android:layout_width="320dp" </ScrollView>
android:layout_height="match_parent"
android:layout_weight="2" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/settingsChevron"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="2pt"
android:alpha="0.4"
android:scaleType="fitEnd"
android:scaleX="1.2"
android:scaleY="1.2"
app:srcCompat="@drawable/ic_baseline_chevron_right_24" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="4pt"
android:layout_marginLeft="4pt"
android:layout_marginTop="6pt"
android:layout_marginEnd="4pt"
android:layout_marginRight="4pt"
android:layout_marginBottom="6pt">
<TextView
android:id="@+id/versionLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Version"
android:textColor="?attr/textDefault" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="2" />
<TextView
android:id="@+id/versionValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.5"
android:text="Unknown"
android:textAlignment="textEnd" />
</TableRow>
</TableLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginTop="2pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="You must re-enable Yggdrasil after modifying Peers or Settings to make any changes effective."
android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>
</LinearLayout> </LinearLayout>

View file

@ -27,7 +27,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8pt" android:layout_margin="8pt"
android:text="Peers" android:text="@string/main_peers"
android:textColor="?attr/textDefault" android:textColor="?attr/textDefault"
android:textSize="24sp" android:textSize="24sp"
android:textStyle="bold" /> android:textStyle="bold" />
@ -47,171 +47,243 @@
app:srcCompat="@drawable/ic_baseline_add_circle_24" /> app:srcCompat="@drawable/ic_baseline_add_circle_24" />
</LinearLayout> </LinearLayout>
<TextView <ScrollView
android:id="@+id/connectedPeersLabel"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent">
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginTop="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="2pt"
android:alpha="0.7"
android:text="Connected Peers"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<TableLayout <LinearLayout
android:id="@+id/connectedPeersTableLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8pt"
android:layout_marginLeft="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:background="@drawable/rounded"
android:divider="#46878787"
android:dividerPadding="4pt"
android:paddingLeft="4pt"
android:paddingTop="2pt"
android:paddingRight="4pt"
android:paddingBottom="2pt"
android:showDividers="middle" />
<TextView
android:id="@+id/configuredPeersLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginTop="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="2pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="Configured Peers"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<TableLayout
android:id="@+id/configuredPeersTableLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8pt"
android:layout_marginLeft="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:background="@drawable/rounded"
android:divider="#46878787"
android:dividerPadding="4pt"
android:paddingLeft="4pt"
android:paddingTop="2pt"
android:paddingRight="4pt"
android:paddingBottom="2pt"
android:showDividers="middle" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginTop="2pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="4pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="Yggdrasil will automatically attempt to connect to configured peers when started. If you configure more than one peer, your device may carry traffic on behalf of other network nodes. Avoid this by configuring only a single peer."
android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<Space
android:layout_width="match_parent"
android:layout_height="32px" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="2pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="Peer Connectivity"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<TableLayout
android:id="@+id/configurationTableLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8pt"
android:layout_marginLeft="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:background="@drawable/rounded"
android:clickable="true"
android:divider="#46878787"
android:dividerPadding="4pt"
android:paddingLeft="4pt"
android:paddingTop="2pt"
android:paddingRight="4pt"
android:paddingBottom="2pt"
android:showDividers="middle">
<TableRow
android:id="@+id/peersTableRow"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:clickable="true" android:orientation="vertical" >
android:paddingStart="4pt"
android:paddingTop="2pt"
android:paddingEnd="4pt"
android:paddingBottom="2pt">
<TextView <TextView
android:id="@+id/multicastLabel" android:id="@+id/connectedPeersLabel"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Search for multicast peers" android:layout_marginStart="16pt"
android:textColor="?attr/textDefault" /> android:layout_marginLeft="16pt"
android:layout_marginTop="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="2pt"
android:alpha="0.7"
android:text="@string/peers_connected_title"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<TableLayout
android:id="@+id/connectedPeersTableLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8pt"
android:layout_marginLeft="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:background="@drawable/rounded"
android:divider="#46878787"
android:dividerPadding="4pt"
android:paddingLeft="4pt"
android:paddingTop="2pt"
android:paddingRight="4pt"
android:paddingBottom="2pt"
android:showDividers="middle" />
<TextView
android:id="@+id/configuredPeersLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginTop="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="2pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="@string/peers_configured_title"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<TableLayout
android:id="@+id/configuredPeersTableLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8pt"
android:layout_marginEnd="8pt"
android:background="@drawable/rounded"
android:divider="#46878787"
android:dividerPadding="4pt"
android:paddingLeft="4pt"
android:paddingTop="2pt"
android:paddingRight="4pt"
android:paddingBottom="2pt"
android:showDividers="middle" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginTop="4pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="4pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="@string/configured_peers_hint"
android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<TextView
android:id="@+id/peers_discovery_link"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginTop="4pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="4pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="@string/peers_discovery_hint"
android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<Space <Space
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="32px" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="2" /> android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="2pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="@string/peer_connectivity_title"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<Switch <TableLayout
android:id="@+id/enableMulticastSwitch" android:id="@+id/configurationTableLayout"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content"
android:layout_marginStart="8pt"
android:layout_marginLeft="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:background="@drawable/rounded"
android:divider="#46878787"
android:dividerPadding="4pt"
android:paddingLeft="4pt"
android:paddingTop="2pt"
android:paddingRight="4pt"
android:paddingBottom="2pt"
android:showDividers="middle">
</TableRow> <TableRow
android:id="@+id/enableMulticastBeaconPanel"
style="@style/SelectableSwitchItemStyle">
</TableLayout> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/discoverable_over_multicast"
android:textColor="?attr/textDefault" />
<TextView <Space
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16pt" android:layout_weight="2" />
android:layout_marginLeft="16pt"
android:layout_marginTop="2pt" <Switch
android:layout_marginEnd="8pt" android:id="@+id/enableMulticastBeacon"
android:layout_marginRight="8pt" android:layout_width="wrap_content"
android:alpha="0.7" android:layout_height="wrap_content" />
android:paddingRight="8pt"
android:text="Multicast peers will be discovered on the same Wi-Fi network or via USB. Data charges may apply when using mobile data. You can prevent data usage in the device settings." </TableRow>
android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppCompat.Small" <TableRow
android:textSize="12sp" /> android:id="@+id/enableMulticastListenPanel"
style="@style/SelectableSwitchItemStyle">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/search_for_multicast_peers"
android:textColor="?attr/textDefault" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="2" />
<Switch
android:id="@+id/enableMulticastListen"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</TableRow>
<TableRow
android:id="@+id/passwordTableRow"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:paddingStart="4pt"
android:paddingTop="4pt"
android:paddingEnd="4pt"
android:paddingBottom="4pt">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/peers_multicast_password_hint"
android:textColor="?attr/textDefault" />
<EditText
android:id="@+id/passwordEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@null"
android:ems="10"
android:hint="@string/tap_to_edit"
android:inputType="textVisiblePassword"
android:padding="0pt"
android:textAlignment="textEnd"
android:textSize="14sp" />
</TableRow>
</TableLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginTop="4pt"
android:layout_marginBottom="4pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="@string/peer_connectivity_hint"
android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
</LinearLayout>
</ScrollView>
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>

View file

@ -1,9 +1,291 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".SettingsActivity"> tools:context=".PeersActivity">
</androidx.constraintlayout.widget.ConstraintLayout> <FrameLayout
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:dividerPadding="4pt"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8pt"
android:text="@string/main_settings"
android:textColor="?attr/textDefault"
android:textSize="24sp"
android:textStyle="bold" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginTop="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="2pt"
android:alpha="0.7"
android:text="@string/node_info"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<TableLayout
android:id="@+id/nodeInfoTableLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8pt"
android:layout_marginLeft="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:background="@drawable/rounded"
android:divider="#46878787"
android:dividerPadding="4pt"
android:paddingLeft="4pt"
android:paddingTop="2pt"
android:paddingRight="4pt"
android:paddingBottom="2pt"
android:showDividers="middle">
<TableRow
android:id="@+id/deviceNameTableRow"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:paddingStart="4pt"
android:paddingTop="4pt"
android:paddingEnd="4pt"
android:paddingBottom="4pt">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/device_name"
android:textColor="?attr/textDefault" />
<EditText
android:id="@+id/deviceNameEntry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@null"
android:ems="10"
android:hint="@string/tap_to_edit"
android:inputType="textPersonName"
android:padding="0pt"
android:textAlignment="textEnd"
android:textSize="14sp" />
</TableRow>
</TableLayout>
<TextView
android:id="@+id/textView6"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginTop="2pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="4pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="@string/node_info_hint"
android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginTop="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="2pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="@string/public_key"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<TableLayout
android:id="@+id/publicKeyTableLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8pt"
android:layout_marginLeft="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:background="@drawable/rounded"
android:divider="#46878787"
android:dividerPadding="4pt"
android:paddingLeft="4pt"
android:paddingTop="2pt"
android:paddingRight="4pt"
android:paddingBottom="2pt"
android:showDividers="middle">
<TableRow style="@style/SelectableItemStyle">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/publicKeyLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="true"
android:fontFamily="monospace"
android:text="@string/public_key"
android:textSize="14sp" />
</LinearLayout>
</TableRow>
</TableLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginTop="2pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="4pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="@string/public_key_hint"
android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<Space
android:layout_width="match_parent"
android:layout_height="32px" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="2pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="@string/settings_config"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/backupTableLayout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8pt"
android:layout_marginLeft="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:background="@drawable/rounded"
android:divider="#46878787"
android:dividerPadding="4pt"
android:paddingLeft="4pt"
android:paddingTop="2pt"
android:paddingRight="4pt"
android:paddingBottom="2pt"
android:showDividers="middle">
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/resetKeysRow"
style="@style/SelectableItemStyle">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/regenerate_keys" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/setKeysRow"
style="@style/SelectableItemStyle">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/set_keys" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/resetConfigurationRow"
style="@style/SelectableItemStyle">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/reset_configuration"
android:textColor="@android:color/holo_red_dark" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginTop="4pt"
android:layout_marginBottom="4pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="@string/reset_configuration_hint"
android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
</LinearLayout>
</ScrollView>
</LinearLayout>
</FrameLayout>
</LinearLayout>

View file

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="10pt"
android:paddingTop="4pt"
android:paddingRight="10pt"
android:paddingBottom="4pt">
<TextView
android:id="@+id/textView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Enter the IP address of the DNS server to add. Note that all DNS requests, including for non-Yggdrasil internet hostnames, will be sent to these servers." />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/addDnsInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4pt"
android:lines="1"
android:hint="8.8.8.8 or 302:7991::53" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</FrameLayout>

View file

@ -16,7 +16,7 @@
android:id="@+id/textView2" android:id="@+id/textView2"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Enter the full URI of the peer to add. Yggdrasil will automatically connect to this peer when started." /> android:text="@string/add_peer_help" />
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -27,6 +27,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4pt" android:layout_marginTop="4pt"
android:lines="1"
android:hint="tcp://address:port" /> android:hint="tcp://address:port" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
</LinearLayout> </LinearLayout>

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="10pt"
android:paddingTop="4pt"
android:paddingRight="10pt"
android:paddingBottom="4pt">
<TextView
android:id="@+id/textView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="This operation will reset your configuration and generate new keys. This is not reversible. Changes will not take effect until the next time Yggdrasil is started." />
</LinearLayout>
</FrameLayout>

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="10pt"
android:paddingRight="10pt"
android:paddingTop="4pt"
android:paddingBottom="4pt">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/private_key_label" />
<EditText
android:id="@+id/private_key"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</FrameLayout>

View file

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/tableRow"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/serverValue"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="4dp"
android:gravity="center_vertical"
android:ellipsize="end"
android:singleLine="true"
android:text=""
android:textColor="?attr/textDefault"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/addButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/addButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:layout_gravity="center_vertical"
android:background="@android:color/transparent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_baseline_add_circle_24" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,29 +1,40 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<TableRow xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/tableRow"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content">
android:padding="4pt">
<TextView <TextView
android:id="@+id/addressValue" android:id="@+id/addressValue"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_marginStart="12dp"
android:text="TextView" android:layout_marginTop="4dp"
android:textColor="?attr/textDefault" /> android:layout_marginEnd="16dp"
android:layout_marginBottom="4dp"
<Space android:gravity="center_vertical"
android:layout_width="wrap_content" android:ellipsize="end"
android:layout_height="wrap_content" android:singleLine="true"
android:layout_weight="1" /> android:text=""
android:textColor="?attr/textDefault"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/deletePeerButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton <ImageButton
android:id="@+id/deletePeerButton" android:id="@+id/deletePeerButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_marginTop="8dp"
android:layout_marginRight="2pt" android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:layout_gravity="center_vertical"
android:background="@android:color/transparent" android:background="@android:color/transparent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_baseline_remove_circle_24" /> app:srcCompat="@drawable/ic_baseline_remove_circle_24" />
</TableRow> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -16,6 +16,8 @@
android:id="@+id/addressLabel" android:id="@+id/addressLabel"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="end"
android:singleLine="true"
android:text="(unknown)" android:text="(unknown)"
android:textColor="?attr/textDefault" /> android:textColor="?attr/textDefault" />
@ -24,6 +26,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:alpha="0.5" android:alpha="0.5"
android:ellipsize="end"
android:singleLine="true"
android:text="TCP peer on unknown" android:text="TCP peer on unknown"
android:textColor="?attr/textDefault" android:textColor="?attr/textDefault"
android:textSize="12sp" /> android:textSize="12sp" />

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View file

@ -0,0 +1,89 @@
<resources>
<string name="app_name">Yggdrasil</string>
<string name="copied_to_clipboard">Скопировано в буфер</string>
<string name="dns_configured_servers_hint">Эти серверы DNS будут использоваться при включении Yggdrasil. Заметьте, что все запросы в DNS, даже о доменах не в Yggdrasil, будут отправляться на эти серверы.</string>
<string name="dns_configured_servers_hint_empty">Yggdrasil не будет переопределять серверы DNS при старте. Все запросы DNS будут разрешаться серверами по умолчанию.</string>
<string name="dns_usable_servers_hint">Эти серверы DNS предоставляются членами коммюнити. Нажмите кнопку + чтобы добавить их в список выше. Долгое нажатие чтобы посмотреть информацию.</string>
<string name="dns_no_configured_servers">Серверы не настроены</string>
<string name="dns_configured_servers">Настроенные серверы</string>
<string name="dns_server_info_revertron">Этот сервер поддерживает работу с обычными доменами ICANN, системой ALFIS, доменами OpenNIC.\n\nКроме того, он блокирует рекламу, системы слежения и зловредные домены.\n\nАдминистратор сервера Revertron.</string>
<string name="dns_server_info_dialog_title">О сервере</string>
<string name="ok">Ок</string>
<string name="cancel">Отмена</string>
<string name="remove">Убрать</string>
<string name="add">Добавить</string>
<string name="dns_add_server_dialog_title">Добавить сервер DNS</string>
<string name="dns_activity_title">DNS</string>
<string name="dns_usable_servers">Рекомендуемые серверы</string>
<string name="dns_fix_chrome_based_browsers">Обхитрить браузеры на основе Chrome</string>
<string name="dns_fix_chrome_based_browsers_hint">Если у вас нет обычного подключения по IPv6, эта опция должна заставить браузеры на движке Chrome всё равно запрашивать записи IPv6.</string>
<string name="dns_fixes">DNS трюки</string>
<string name="dns_no_servers">Нет серверов</string>
<string name="dns_one_server">1 сервер</string>
<string name="dns_many_servers">%d сервера/серверов</string>
<string name="dns_remove_title">Убрать %s?</string>
<string name="dns_already_added_server">Этот сервер уже добавлен.</string>
<string name="main_no_connectivity">Включено (Нет подключения)</string>
<string name="main_enabled">Подключено</string>
<string name="main_disabled">Выключено</string>
<string name="main_no_peers">Нет пиров</string>
<string name="main_one_peer">1 пир</string>
<string name="main_many_peers">%d пира/пиров</string>
<string name="main_add_some_peers_title">Внимание</string>
<string name="main_add_some_peers_message">Не настроено ни одного пира. Если не будет обнаруживаемых пиров в этой сети, то вам надо будет добавить пир вручную, чтобы подключение к Yggdrasil работало как положено.</string>
<string name="peers_add_peer">Добавить пира в конфиг</string>
<string name="peers_add">Добавить</string>
<string name="peers_remove_title">Убрать %s?</string>
<string name="peers_remove">Убрать</string>
<string name="peers_no_configured_title">Пиры не добавлены</string>
<string name="peers_configured_title">Добавленные пиры</string>
<string name="peers_no_connected_title">Нет подключенных пиров</string>
<string name="peers_connected_title">Подключенные пиры</string>
<string name="settings_warning_title">Внимание</string>
<string name="settings_config">Конфигурация</string>
<string name="settings_reset">Сброс</string>
<string name="main_status">Состояние</string>
<string name="main_enable_yggdrasil">Включить Yggdrasil</string>
<string name="main_network_info">Адрес и сеть</string>
<string name="main_not_available">Н</string>
<string name="main_ip">Адрес</string>
<string name="main_subnet">Подсеть</string>
<string name="main_configuration">Конфигурация</string>
<string name="main_peers">Пиры</string>
<string name="main_dns_servers">Серверы DNS</string>
<string name="main_settings">Настройки</string>
<string name="main_version">Версия</string>
<string name="main_unknown">Не известно</string>
<string name="main_bottom_warning">Вы должны перезапустить Yggdrasil после изменения пиров, серверов DNS или настроек, чтобы изменения вступили в силу.</string>
<string name="peer_connectivity_title">Подключения пиров</string>
<string name="discoverable_over_multicast">Находимый через multicast</string>
<string name="search_for_multicast_peers">Искать пиров через multicast</string>
<string name="configured_peers_hint">Yggdrasil будет пытаться подключаться к этим пирам автоматически. Если вы добавите несколько пиров, ваше устройство может быть использовано для переноса данных между другими узлами сети. Чтобы этого избежать настройте только один пир.</string>
<string name="peers_discovery_hint">Вы можете найти публичные пиры <a href="https://publicpeers.neilalexander.dev/">по этой ссылке</a>.</string>
<string name="peer_connectivity_hint">Пиры могут быть найдены с помощью Multicast если они находятся в той же Wi-Fi сети, либо через USB. У них должен быть одинаковый пароль. Трафик в мобильной сети может быть платным. Вы можете отключить мобильные данные в настройках устройства.</string>
<string name="peers_multicast_password_hint">Пароль</string>
<string name="node_info">Об узле</string>
<string name="device_name">Название устройства</string>
<string name="tap_to_edit">Нажмите для изменения</string>
<string name="node_info_hint">Эта информация публична и может появиться на картах сети.</string>
<string name="public_key">Публичный ключ</string>
<string name="public_key_hint">Ваш публичный ключ идентифицирует вас в сети. Его распространение безопасно.</string>
<string name="regenerate_keys">Сбросить ключи (и адрес IPv6)</string>
<string name="reset_configuration">Сбросить настройки</string>
<string name="reset_configuration_hint">Сброс создаст полностью новые настройки. Это изменит ваш публичный ключ и адрес IP.</string>
<string name="tile_disabled">Выключено</string>
<string name="tile_enabled">Включено (Нет подключения)</string>
<string name="tile_connected">Подключено</string>
<string name="location_amsterdam">Амстердам, Нидерланды</string>
<string name="location_frankfurt">Франкфурт, Германия</string>
<string name="location_bratislava">Братислава, Словакия</string>
<string name="location_buffalo">Буффало, США</string>
<string name="channel_name">Сервис VPN</string>
<string name="channel_description">Главный канал нотификаций сервиса</string>
<string name="permission_notification_text">Нажмите здесь чтобы включить Yggdrasil.</string>
<string name="add_peer_help">Введите полный URI пира для добавления. Yggdrasil будет автоматически подключаться к нему при запуске.</string>
<string name="private_key_label">Приватный ключ:</string>
<string name="set_keys">Установить свой ключ</string>
<string name="save">Сохранить</string>
<string name="no_browser_found_toast">Не найден браузер для открытия ссылки!</string>
</resources>

View file

@ -5,6 +5,7 @@
<color name="purple_700">#FF3700B3</color> <color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color> <color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color> <color name="teal_700">#FF018786</color>
<color name="green">#5FBF9F</color>
<color name="black">#FF000000</color> <color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color> <color name="white">#FFFFFFFF</color>
<color name="hintlight">#F2F1F5</color> <color name="hintlight">#F2F1F5</color>

View file

@ -1,3 +1,89 @@
<resources> <resources>
<string name="app_name">Yggdrasil</string> <string name="app_name">Yggdrasil</string>
<string name="copied_to_clipboard">Copied to clipboard</string>
<string name="dns_configured_servers_hint">Use these DNS servers while Yggdrasil is running. Note that all DNS requests, including for non-Yggdrasil internet hostnames, will be sent to these servers.</string>
<string name="dns_configured_servers_hint_empty">Yggdrasil will not configure any DNS servers when the service starts. All DNS requests will be resolved by the default resolver.</string>
<string name="dns_usable_servers_hint">These DNS servers are provided by community members. Click the + button to add them to the list above. Long-tap to see more info.</string>
<string name="dns_no_configured_servers">No servers configured</string>
<string name="dns_configured_servers">Configured servers</string>
<string name="dns_server_info_revertron">The server supports resolving regular ICANN domains, ALFIS domains, OpenNIC domains.\n\nAlso, it blocks ads, analytics and malware websites.\n\nThe server is run by Revertron.</string>
<string name="dns_server_info_dialog_title">Server info</string>
<string name="ok">OK</string>
<string name="cancel">Cancel</string>
<string name="remove">Remove</string>
<string name="add">Add</string>
<string name="dns_add_server_dialog_title">Add DNS server</string>
<string name="dns_activity_title">DNS</string>
<string name="dns_usable_servers">Usable servers</string>
<string name="dns_fix_chrome_based_browsers">Fix Chrome-based browsers</string>
<string name="dns_fix_chrome_based_browsers_hint">If you do not have IPv6 internet connectivity, this option should help Chrome-based browsers to resolve Yggdrasil domain names correctly.</string>
<string name="dns_fixes">DNS fixes</string>
<string name="dns_no_servers">No servers</string>
<string name="dns_one_server">1 server</string>
<string name="dns_many_servers">%d servers</string>
<string name="dns_remove_title">Remove %s?</string>
<string name="dns_already_added_server">This server is already added.</string>
<string name="main_no_connectivity">Enabled (No connectivity)</string>
<string name="main_enabled">Connected</string>
<string name="main_disabled">Not enabled</string>
<string name="main_no_peers">No peers</string>
<string name="main_one_peer">1 peer</string>
<string name="main_many_peers">%d peers</string>
<string name="main_add_some_peers_title">Note</string>
<string name="main_add_some_peers_message">No peers are configured. If there are no multicast peers nearby, you will need to manually configure peers in order for Yggdrasil to connect and work properly.</string>
<string name="peers_add_peer">Add Configured Peer</string>
<string name="peers_add">Add</string>
<string name="peers_remove_title">Remove %s?</string>
<string name="peers_remove">Remove</string>
<string name="peers_no_configured_title">No peers currently configured</string>
<string name="peers_configured_title">Configured Peers</string>
<string name="peers_no_connected_title">No peers currently connected</string>
<string name="peers_connected_title">Connected Peers</string>
<string name="settings_warning_title">Warning</string>
<string name="settings_config">Config</string>
<string name="settings_reset">Reset</string>
<string name="main_status">Status</string>
<string name="main_enable_yggdrasil">Enable Yggdrasil</string>
<string name="main_network_info">Network info</string>
<string name="main_not_available">N/A</string>
<string name="main_ip">IP</string>
<string name="main_subnet">Subnet</string>
<string name="main_configuration">Configuration</string>
<string name="main_peers">Peers</string>
<string name="main_dns_servers">DNS servers</string>
<string name="main_settings">Settings</string>
<string name="main_version">Version</string>
<string name="main_unknown">Unknown</string>
<string name="main_bottom_warning">You must re-enable Yggdrasil after modifying Peers, DNS servers or Settings to make any changes effective.</string>
<string name="peer_connectivity_title">Peer Connectivity</string>
<string name="discoverable_over_multicast">Discoverable over multicast</string>
<string name="search_for_multicast_peers">Search for multicast peers</string>
<string name="configured_peers_hint">Yggdrasil will automatically attempt to connect to configured peers when started. If you configure more than one peer, your device may carry traffic on behalf of other network nodes. Avoid this by configuring only a single peer.</string>
<string name="peers_discovery_hint">You can find public peers by opening <a href="https://publicpeers.neilalexander.dev/">this link</a>.</string>
<string name="peer_connectivity_hint">Multicast peers will be discovered on the same Wi-Fi network or via USB. They must have the same password. Data charges may apply when using mobile data. You can prevent data usage in the device settings.</string>
<string name="peers_multicast_password_hint">Password</string>
<string name="node_info">Node Info</string>
<string name="device_name">Device Name</string>
<string name="tap_to_edit">Tap to edit</string>
<string name="node_info_hint">Information entered here is public and may be shown on network maps.</string>
<string name="public_key">Public Key</string>
<string name="public_key_hint">Your public key forms your identity on the network. It is safe to be shared.</string>
<string name="regenerate_keys">Regenerate keys (and IPv6-address)</string>
<string name="reset_configuration">Reset configuration</string>
<string name="reset_configuration_hint">Resetting will overwrite with newly generated configuration. Your public keys and IP address on the network will change.</string>
<string name="tile_disabled">Disabled</string>
<string name="tile_enabled">Enabled (No connectivity)</string>
<string name="tile_connected">Connected</string>
<string name="location_amsterdam">Amsterdam, NL</string>
<string name="location_frankfurt">Frankfurt, DE</string>
<string name="location_bratislava">Bratislava, SK</string>
<string name="location_buffalo">Buffalo, US</string>
<string name="channel_name">VPN Service</string>
<string name="channel_description">Main channel for foreground notification</string>
<string name="permission_notification_text">Tap here to enable Yggdrasil.</string>
<string name="add_peer_help">Enter the full URI of the peer to add. Yggdrasil will automatically connect to this peer when started.</string>
<string name="private_key_label">Private key:</string>
<string name="set_keys">Set your own key</string>
<string name="save">Save</string>
<string name="no_browser_found_toast">No browser found to open the URL!</string>
</resources> </resources>

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="SelectableItemStyle">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">match_parent</item>
<item name="android:background">?android:attr/selectableItemBackground</item>
<item name="android:paddingStart">8dp</item>
<item name="android:paddingEnd">8dp</item>
<item name="android:paddingTop">10dp</item>
<item name="android:paddingBottom">10dp</item>
</style>
<style name="SelectableSwitchItemStyle">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">match_parent</item>
<item name="android:background">?android:attr/selectableItemBackground</item>
<item name="android:paddingStart">8dp</item>
<item name="android:paddingEnd">8dp</item>
<item name="android:paddingTop">8dp</item>
<item name="android:paddingBottom">8dp</item>
</style>
</resources>

View file

@ -18,4 +18,8 @@
<item name="tableBackgroundColor">@color/white</item> <item name="tableBackgroundColor">@color/white</item>
<item name="textDefault">@color/black</item> <item name="textDefault">@color/black</item>
</style> </style>
<style name="YggdrasilDialogs" parent="@style/Theme.MaterialComponents.DayNight.Dialog">
<item name="colorPrimary">@color/green</item>
</style>
</resources> </resources>

View file

@ -1,12 +1,12 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = "1.5.0" ext.kotlin_version = '1.9.20'
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath "com.android.tools.build:gradle:4.2.1" classpath 'com.android.tools.build:gradle:8.1.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
@ -18,7 +18,6 @@ allprojects {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
jcenter() // Warning: this repository is going to shut down soon
} }
} }

View file

@ -0,0 +1,4 @@
* Added quick-settings tile for fast on/off switching
* Added notification with the connection status (it is needed for quick-settings tile)
* Fixed small UI bugs
* Updated Yggdrasil library to 0.4.7

View file

@ -0,0 +1,3 @@
* Updated core Yggdrasil library to 0.5.1
* Updated UI to reflect changes in new version
* Fixed small bugs in UI

View file

@ -0,0 +1 @@
* Updated core Yggdrasil library to 0.5.4

View file

@ -0,0 +1 @@
* Updated core Yggdrasil library to 0.5.6

View file

@ -0,0 +1,14 @@
Updated core library to 0.5.7, presenting these changes:
Added
WebSocket support for peerings, by using the new ws:// scheme in Listen and Peers
Additionally, the wss:// scheme can be used to connect to a WebSocket peer behind a HTTPS reverse proxy
Changed
On Linux, the TUN adapter now uses vectorised reads/writes where possible, which should reduce the amount of CPU time spent on syscalls and potentially improve throughput
Link error handling has been improved and various link error messages have been rewritten to be clearer
Upgrade dependencies
Fixed
Multiple multicast connections to the same remote machine should now work correctly
You may get two connections in some cases, one inbound and one outbound, this is known and will not cause problems

View file

@ -0,0 +1,23 @@
Updated core library to 0.5.9, presenting these changes:
Changed
The routing algorithm has been updated with RTT-aware link costing, which should prefer lower latency links over higher latency links where possible
The calculated cost is an average of the link RTT, but newly established links are costed higher to begin with, such that unstable peerings can be avoided
Link costs are only used where multiple next-hops are available and will be ignored if there is only one loop-free path to the destination
This is protocol-compatible with existing v0.5.x nodes but will have the best results when peering with nodes that are also running the latest version
The getPeers endpoint will now report the calculated link cost for each given peer
Upgrade dependencies
Fixed
Multicast discovery should now work again when building Yggdrasil as an Android framework
Multicast discovery will now correctly ignore interfaces that are not marked as running
Ephemeral links, such as those added by multicast, will no longer try to reconnect in a fast loop, fixing a high CPU issue
The TUN interface will no longer stop working when hitting a segment read error from vectorised reads
The AllowedPublicKeys option will once again no longer apply to multicast peerings, as was originally intended
A potential panic when shutting down peering links has been fixed
A redundant system call for setting MTU on OpenBSD has been removed
Fixes in Android app
Fixed occasional crash on start/stop
Updated some dependencies
Updated Android API to 34

View file

@ -0,0 +1,19 @@
Updated core library to 0.5.12, presenting these changes:
Fixed
A timing regression which causes a higher level of idle protocol traffic on each peering has been fixed
Fixes in Android app
Some UI fixes and improvements
Updated some dependencies
Updates from previous versions:
Changed
The parent selection algorithm now only chooses a new parent if there is a larger cost benefit to doing so, which should help to stabilise the tree
The bloom filters are now repropagated periodically, to avoid nodes getting stuck with bad state
Fixed
A memory leak caused by missed cleanup of the peer response map has been fixed
Other bug fixes with bloom filter propagation for off-tree filters and zero vs one bits
TLS-based peering connections now support TLS 1.2 again

View file

@ -0,0 +1,9 @@
Yggdrasil is an early-stage implementation of a fully end-to-end encrypted IPv6 network.
It is lightweight, self-arranging, supported on multiple platforms and allows pretty much any IPv6-capable application to communicate securely with other Yggdrasil nodes.
Yggdrasil does not require you to have IPv6 Internet connectivity - it also works over IPv4.
This app allows you to connect to Yggdrasil Network and use any service located in this network. It works as VPN service, but all your usual traffic will go trough your provider, not through Yggdrasil Network.
Also, it is not a goal of the Yggdrasil project to provide anonymity. Direct peers over the Internet will be able to see your IP address and may be able to use this information to determine your location or identity. Multicast-discovered peerings on the same network will typically expose your device MAC address. Other nodes on the network may be able to discern some information about which nodes you are peered with.
All traffic sent across the Yggdrasil network is encrypted end-to-end. Assuming that our crypto is solid, it cannot be decrypted or read by any intermediate nodes, and can only be decrypted by the recipient for which it was intended. However, please note that Yggdrasil has not been officially externally audited.

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View file

@ -0,0 +1 @@
Official implementation for connecting to the Yggdrasil Network from Android

View file

@ -0,0 +1 @@
Yggdrasil

View file

@ -0,0 +1,4 @@
* Добавлена кнопка в шторку для быстрого включения/выключения
* Добавлена компактная нотификация со статусом подключения (требуется для работы кнопки в шторке)
* Исправлены некоторые баги в интерфейсе
* Обновлена библиотека Yggdrasil до 0.4.7

View file

@ -0,0 +1,3 @@
* Обновлена библиотека Yggdrasil до 0.5.1
* Обновлен интерфейс в соответствии с новой версией библиотеки
* Сделаны небольшие исправления в интерфейсе

View file

@ -0,0 +1 @@
* Обновлена библиотека Yggdrasil до 0.5.4

View file

@ -0,0 +1 @@
* Обновлена библиотека Yggdrasil до 0.5.6

View file

@ -0,0 +1,14 @@
Обновлена основная библиотека до версии 0.5.7, в которой представлены следующие изменения:
Добавлено
Поддержка WebSocket для пиринга с использованием новой схемы ws:// в Listen и Peers
Кроме того, схему wss:// можно использовать для подключения к узлам WebSocket за обратным прокси-сервером HTTPS вроде Nginx
Изменено
В Linux адаптер TUN теперь использует векторизованную чтение/запись, где это возможно, что должно сократить количество времени ЦП, затрачиваемого на системные вызовы, и потенциально повысить пропускную способность
Улучшена обработка ошибок соединения, а различные сообщения об ошибках соединения были переписаны для большей ясности
Обновление зависимостей
Исправлено
Несколько мультикаст подключений к одной и той же удаленной машине теперь должны работать правильно
В некоторых случаях вы можете получить два подключения, одно входящее и одно исходящее, это известное поведение и не вызовет проблем

View file

@ -0,0 +1,23 @@
Обновлена основная библиотека до версии 0.5.9, в которой представлены следующие изменения:
Изменено
Алгоритм маршрутизации был обновлен с учетом стоимости соединения с RTT, что должно отдавать предпочтения соединениям с меньшей задержкой соединениям с большей задержкой, когда это возможно
Расчетная стоимость представляет собой среднее значение RTT соединения, но новые соединения изначально оцениваются выше, так что можно избежать проблем с нестабильными узлами
Стоимость соединения используется только при наличии нескольких следующих переходов и будет игнорироваться, если есть только один путь без петель к месту назначения
Эта версия совместима с существующими узлами v0.5.x, но будет иметь наилучшие результаты при пиринге с узлами, которые также работают под управлением последней версии
Команда getPeers теперь будет сообщать рассчитанную стоимость соединения для каждого заданного пира
Обновлены зависимости
Исправлено
Обнаружение локальных пиров теперь должно снова работать при сборке Yggdrasil как фреймворка Android
Обнаружение локальных пиров теперь будет правильно игнорировать интерфейсы, которые не помечены как работающие
Эфемерные соединения, такие как добавленные мультикастом, больше не будут пытаться быстро переподключаться в цикле, устранена проблема высокой загрузки ЦП
Интерфейс TUN больше не будет прекращать работу при срабатывании ошибки чтения пакета с помощью векторизованного чтения
Опция AllowedPublicKeys снова больше не будет применяться к локальным пирам, как изначально предполагалось
Потенциальный краш при отключении пиринговых соединений был исправлен
Избыточный системный вызов для установки MTU в OpenBSD был удален
Исправления в приложении для Android
Исправлен случайный сбой при запуске/остановке
Обновлены некоторые зависимости
Обновлен Android API до 34

View file

@ -0,0 +1,19 @@
Обновлена основная библиотека до версии 0.5.12, в которой представлены следующие изменения:
Исправлено
Исправлена регрессия синхронизации, которая приводит к более высокому уровню служебного трафика в простое
Исправления в приложении Android
Исправления и улучшения пользовательского интерфейса
Обновлены некоторые зависимости
Обновления с предыдущих версий:
Изменено
Алгоритм выбора родителя теперь выбирает нового родителя только в том случае, если это дает большую экономическую выгоду, что должно помочь стабилизировать дерево
Фильтры Блума теперь периодически распространяются повторно, чтобы избежать застревания узлов в плохом состоянии
Исправлено
Утечка памяти, вызванная пропущенной очисткой карты ответов пиров
Другие исправления ошибок с распространением фильтра Блума для фильтров вне дерева
Пиринг с использованием TLS теперь снова поддерживают TLS 1.2

View file

@ -0,0 +1,9 @@
Yggdrasil это рабочая реализация полностью зашифрованной сети IPv6.
Она лёгкая, само-организующаяся, поддерживающая множество платформ, и позволяющая любому ПО, способному работать с IPv6, свободно коммуницировать с другими узлами сети Yggdrasil.
Yggdrasil не требует Интернет-подключения по IPv6 - она работает и поверх IPv4.
Это приложение позволяет вам подключиться к Yggdrasil Network и пользоваться любыми сервисами, расположенными в этой сети. Оно работает как сервис VPN, но весь ваш обычный Интернет-трафик будет идти через вашего провайдера, не через сеть Yggdrasil.
Заметьте, что анонимность не является целью Yggdrasil. Прямые пиры через Интернет смогут видеть ваш настоящий адрес IP и смогут использовать эту информацию для определения вашего местоположения или идентификации. Пиры, подключающиеся с помощью multicast, скорее всего смогут узнать ваш MAC-адрес. Некоторые узлы в сети могут вычислить через какие узлы вы входите в сеть Yggdrasil.
Весь трафик, пересылаемый через сеть Yggdrasil зашифрован от точки к точке. Предполагая, что наше шифрование реализовано правильно, трафик не может быть расшифрован или прочитан промежуточными узлами, иможет быть расшифрован только адресатом. Однако, просим заметить, что Yggdrasil не подвергался официальному внешнему аудиту.

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View file

@ -0,0 +1 @@
Официальный клиент для подключения к Yggdrasil Network с устройств Android

View file

@ -0,0 +1 @@
Yggdrasil

View file

@ -16,4 +16,8 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# https://developer.android.com/topic/libraries/support-library/androidx-rn # https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete": # Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official kotlin.code.style=official
#android.enableR8.fullMode=false
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false

View file

@ -1,6 +1,6 @@
#Mon Jun 14 15:11:35 BST 2021 #Mon Nov 27 01:27:23 CET 2023
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

1
libs/yggdrasil-go Submodule

@ -0,0 +1 @@
Subproject commit 213f72b8403ff55a5e38a0fa7d1cd0a093ac4666

56
readme.md Normal file
View file

@ -0,0 +1,56 @@
Yggdrasil Android
-----------------
Yggdrasil is an early-stage implementation of a fully end-to-end encrypted IPv6 network. It is lightweight, self-arranging, supported on multiple platforms and allows pretty much any IPv6-capable application to communicate securely with other Yggdrasil nodes. Yggdrasil does not require you to have IPv6 Internet connectivity - it also works over IPv4.
This app allows you to connect to Yggdrasil Network and use any service located in this network. It works as VPN service, but all your usual traffic will go trough your provider, not through Yggdrasil Network.
Also, it is not a goal of the Yggdrasil project to provide anonymity. Direct peers over the Internet will be able to see your IP address and may be able to use this information to determine your location or identity. Multicast-discovered peerings on the same network will typically expose your device MAC address. Other nodes on the network may be able to discern some information about which nodes you are peered with.
All traffic sent across the Yggdrasil network is encrypted end-to-end. Assuming that our crypto is solid, it cannot be decrypted or read by any intermediate nodes, and can only be decrypted by the recipient for which it was intended. However, please note that Yggdrasil has not been officially externally audited.
## Download
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/eu.neilalexander.yggdrasil/)
Or get the APK from the [Releases Section](https://github.com/yggdrasil-network/yggdrasil-android/releases/latest).
## Build Instructions
* install gomobile
```bash
go install golang.org/x/mobile/cmd/gomobile@latest
```
* build yggdrasil-go for android:
```
git clone https://github.com/yggdrasil-network/yggdrasil-go /tmp/yggdrasil-go
cd /tmp/yggdrasil-go
./contrib/mobile/build -a
```
* clone yggdrasil for android and copy over the built go library
```
git clone https://github.com/yggdrasil-network/yggdrasil-android /tmp/yggdrasil-android
mkdir /tmp/yggdrasil-android/app/libs
cp /tmp/yggdrasil-go/yggdrasil.aar /tmp/yggdrasil-android/app/libs/
```
* build yggdrasil-android
```
cd /tmp/yggdrasil-android
./gradlew assembleRelease
```
note: you will need to use jdk-11 as jdk-16 `"doesn't work" ™`
on debian/ubuntu you can set which jdk used with the `JAVA_HOME` env var:
```
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64/
```