mirror of
https://github.com/yggdrasil-network/crispa-android.git
synced 2024-11-13 22:11:03 +00:00
Merge branch 'master' of https://github.com/vikulin/yggdrasil-android
This commit is contained in:
commit
225487eacc
1
.gitignore
vendored
1
.gitignore
vendored
@ -15,3 +15,4 @@
|
|||||||
/yggdrasil/yggdrasil-sources.jar
|
/yggdrasil/yggdrasil-sources.jar
|
||||||
/app/src/main/assets/yggdrasil-0.3.8-linux-arm64
|
/app/src/main/assets/yggdrasil-0.3.8-linux-arm64
|
||||||
/app/src/main/assets/yggdrasil-0.3.8-linux-armhf
|
/app/src/main/assets/yggdrasil-0.3.8-linux-armhf
|
||||||
|
/.idea/
|
||||||
|
@ -1,6 +1,22 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
<code_scheme name="Project" version="173">
|
<code_scheme name="Project" version="173">
|
||||||
<JetCodeStyleSettings>
|
<JetCodeStyleSettings>
|
||||||
|
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||||
|
<value>
|
||||||
|
<package name="java.util" alias="false" withSubpackages="false" />
|
||||||
|
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
|
||||||
|
<package name="io.ktor" alias="false" withSubpackages="true" />
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
<option name="PACKAGES_IMPORT_LAYOUT">
|
||||||
|
<value>
|
||||||
|
<package name="" alias="false" withSubpackages="true" />
|
||||||
|
<package name="java" alias="false" withSubpackages="true" />
|
||||||
|
<package name="javax" alias="false" withSubpackages="true" />
|
||||||
|
<package name="kotlin" alias="false" withSubpackages="true" />
|
||||||
|
<package name="" alias="true" withSubpackages="true" />
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
</JetCodeStyleSettings>
|
</JetCodeStyleSettings>
|
||||||
<codeStyleSettings language="XML">
|
<codeStyleSettings language="XML">
|
||||||
|
@ -3,15 +3,15 @@ apply plugin: 'kotlin-android'
|
|||||||
apply plugin: 'kotlin-android-extensions'
|
apply plugin: 'kotlin-android-extensions'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 29
|
compileSdkVersion 30
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "io.github.chronosx88.yggdrasil"
|
applicationId "io.github.chronosx88.yggdrasil"
|
||||||
minSdkVersion 21
|
minSdkVersion 15
|
||||||
targetSdkVersion 29
|
targetSdkVersion 30
|
||||||
versionCode 3
|
versionCode 16
|
||||||
versionName "1.3"
|
versionName "1.6"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
project.ext.set("archivesBaseName", project.getParent().name+"-"+versionName)
|
setProperty("archivesBaseName", project.getParent().name+"-"+versionName)
|
||||||
}
|
}
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
release {
|
release {
|
||||||
@ -36,6 +36,16 @@ android {
|
|||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = '1.8'
|
jvmTarget = '1.8'
|
||||||
}
|
}
|
||||||
|
lintOptions {
|
||||||
|
checkReleaseBuilds false
|
||||||
|
// Or, if you prefer, you can continue to check for errors in release builds,
|
||||||
|
// but continue the build even when errors are found:
|
||||||
|
abortOnError false
|
||||||
|
}
|
||||||
|
packagingOptions {
|
||||||
|
exclude 'META-INF/LICENSE'
|
||||||
|
exclude 'META-INF/NOTICE'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task ndkBuild(type: Exec) {
|
task ndkBuild(type: Exec) {
|
||||||
@ -48,22 +58,23 @@ gradle.projectsEvaluated {
|
|||||||
tasks.compileDebugKotlin.dependsOn(ndkBuild)
|
tasks.compileDebugKotlin.dependsOn(ndkBuild)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
implementation project(path: ':yggdrasil')
|
implementation project(path: ':yggdrasil')
|
||||||
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||||
implementation "androidx.preference:preference-ktx:1.1.1"
|
implementation "androidx.preference:preference-ktx:1.1.1"
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7'
|
||||||
implementation 'com.google.android.material:material:1.3.0-alpha01'
|
implementation 'com.google.android.material:material:1.3.0-alpha04'
|
||||||
implementation 'com.google.code.gson:gson:2.8.6'
|
implementation 'com.google.code.gson:gson:2.8.6'
|
||||||
implementation 'com.hbb20:ccp:2.4.0'
|
implementation 'com.hbb20:ccp:2.4.0'
|
||||||
|
implementation 'com.vincentbrison.openlibraries.android:dualcache:3.1.1'
|
||||||
|
implementation 'com.vincentbrison.openlibraries.android:dualcache-jsonserializer:3.1.1'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.13.1'
|
||||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
androidTestImplementation 'androidx.test:runner:1.3.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||||
}
|
}
|
@ -14,6 +14,12 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
tools:ignore="GoogleAppIndexingWarning">
|
tools:ignore="GoogleAppIndexingWarning">
|
||||||
|
<activity
|
||||||
|
android:name=".AboutActivity"
|
||||||
|
android:parentActivityName=".MainActivity"
|
||||||
|
android:label="@string/title_activity_about"
|
||||||
|
android:theme="@style/AppTheme.NoActionBar"
|
||||||
|
android:screenOrientation="portrait"/>
|
||||||
<activity
|
<activity
|
||||||
android:name=".PeerListActivity"
|
android:name=".PeerListActivity"
|
||||||
android:parentActivityName=".MainActivity"
|
android:parentActivityName=".MainActivity"
|
||||||
@ -26,6 +32,12 @@
|
|||||||
android:label="@string/title_activity_dns_list"
|
android:label="@string/title_activity_dns_list"
|
||||||
android:theme="@style/AppTheme.NoActionBar"
|
android:theme="@style/AppTheme.NoActionBar"
|
||||||
android:screenOrientation="portrait"/>
|
android:screenOrientation="portrait"/>
|
||||||
|
<activity
|
||||||
|
android:name=".CopyLocalNodeInfoActivity"
|
||||||
|
android:parentActivityName=".MainActivity"
|
||||||
|
android:label="@string/title_activity_copy_local_node_info"
|
||||||
|
android:theme="@style/AppTheme.NoActionBar"
|
||||||
|
android:screenOrientation="portrait"/>
|
||||||
<service
|
<service
|
||||||
android:name=".YggdrasilTunService"
|
android:name=".YggdrasilTunService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
|
@ -0,0 +1,205 @@
|
|||||||
|
package io.github.chronosx88.yggdrasil
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.Html
|
||||||
|
import android.text.method.LinkMovementMethod
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.text.HtmlCompat
|
||||||
|
|
||||||
|
class AboutActivity:AppCompatActivity() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
@JvmStatic var about = "<body style=\"background-color:#343334;\">\n" +
|
||||||
|
"<p class=\"western\" align=\"center\">\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\" color=\"#ffffff\">\n" +
|
||||||
|
"Github repo <a href=\"https://github.com/yggdrasil-network/yggdrasil-android\"><u>https://github.com/yggdrasil-network/yggdrasil-android</u></a>.\n" +
|
||||||
|
"</font>\n" +
|
||||||
|
"<p class=\"western\" ><font style=\"font-size: 14pt\" color=\"#ffffff\"><b>Introduction</b></font></p>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">Yggdrasil is an early-stage implementation of a fully end-to-end encrypted IPv6 network. I\n" +
|
||||||
|
"t is lightweight, self-arranging, supported on multiple platforms and allows pretty much any IPv6-capable application to communicate securely with other Yggdrasil nodes.\n" +
|
||||||
|
"Yggdrasil does not require you to have IPv6 Internet connectivity - it also works over IPv4.</font></p>\n" +
|
||||||
|
"<p class=\"western\"><font style=\"font-size: 14pt\" color=\"#ffffff\"><b>Credit</b></font></p>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\" >Developers: vikulin <a href=\"mailto:vadym.vikulin@gmail.com\">vadym.vikulin@gmail.com</a>, ChronosX88 <a href=\"mailto:chronosx88@gmail.com\">chronosx88@gmail.com</a></font></p>\n" +
|
||||||
|
"\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">QA: jorektheglitch <a href=\"mailto:entressi@yandex.ru\">entressi@yandex.ru</a>\n" +
|
||||||
|
"</font></p>\n" +
|
||||||
|
"\n" +
|
||||||
|
"<font class=\"western\" style=\"font-size: 14pt\" color=\"#ffffff\"><b>Is Yggdrasil safe?</b></font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">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.\n" +
|
||||||
|
"</font></p>\n" +
|
||||||
|
"\n" +
|
||||||
|
"<font class=\"western\" style=\"font-size: 14pt\" color=\"#ffffff\"><b>Is Yggdrasil stable?</b></font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">Our official stance is that it is still alpha software. Expect things to not be wholly smooth, and expect to have to upgrade often to the latest builds. That said, there is a small community of users who have not experienced any stability problems so far. Yggdrasil very rarely crashes.\n" +
|
||||||
|
"</font></p>\n" +
|
||||||
|
"\n" +
|
||||||
|
"<font class=\"western\" style=\"font-size: 14pt\" color=\"#ffffff\"><b>Is Yggdrasil anonymous?</b></font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">It is not a goal of the Yggdrasil project to provide anonymity. Your direct peers may be able to determine your location if, for example, you are peering over the Internet.\n" +
|
||||||
|
"</font></p>\n" +
|
||||||
|
"\n" +
|
||||||
|
"<font class=\"western\" style=\"font-size: 14pt\" color=\"#ffffff\"><b>Does Yggdrasil work on my platform?</b></font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">Quite likely! Take a look at the <a href=\"https://yggdrasil-network.github.io/platforms.html\"><u>Platforms</u></a> page - you’ll find platform-specific notes there.\n" +
|
||||||
|
"</font></p>\n" +
|
||||||
|
"\n" +
|
||||||
|
"<font class=\"western\" style=\"font-size: 14pt\" color=\"#ffffff\"><b>Does Yggdrasil require IPv6?</b></font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">Your system must be IPv6-capable, which just about all modern operating systems are.\n" +
|
||||||
|
"While Yggdrasil does transport only IPv6 traffic internally, you do not need an IPv6 internet connection to peer with other Yggdrasil users. You can peer with other Yggdrasil nodes over either IPv4 or IPv6.\n" +
|
||||||
|
"</font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font class=\"western\" style=\"font-size: 14pt\" color=\"#ffffff\"><b>Will Yggdrasil conflict with my network routing?</b></font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">Yggdrasil uses the 0200::/7 range, which is a range deprecated by the IETF. It has been deprecated since 2004, pending changes to an RFC which simply never materialised 14 years later. It was decided to use this range instead of fc00::/7 (which is more typically allocated to private networks) in order to prevent conflicts with existing ULA ranges.\n" +
|
||||||
|
"As long as you are not using this deprecated address range on your network, you will not experience any routing conflicts.\n" +
|
||||||
|
"</font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font class=\"western\" style=\"font-size: 14pt\" color=\"#ffffff\"><b>Can the network be crawled?</b></font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">Currently it is possible to crawl the network to reveal the spanning tree relationships by querying nodes in the DHT. This is how <a href=\"http://51.15.204.214/\"><u>the network map</u></a> and popularity contest are generated today. However, it is considered a design weakness that this is even possible currently and will hopefully be fixed in the future.\n" +
|
||||||
|
"</font></p>\n" +
|
||||||
|
"\n" +
|
||||||
|
"<font class=\"western\" style=\"font-size: 14pt\" color=\"#ffffff\"><b>Can I run a crawler?</b></font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">Please don’t run your own network crawler. Crawlers generate a lot of network-wide protocol traffic and effect is amplified by each additional crawler. If you really really really want information about the network today, use an <a href=\"http://[301:4541:2f84:1188:216:3eff:feb6:65a3]:3001/nodeinfo.json\"><u>existing data source within the network</u></a> instead of crawling yourself.\n" +
|
||||||
|
"</font></p>\n" +
|
||||||
|
"\n" +
|
||||||
|
"<font class=\"western\" style=\"font-size: 14pt\" color=\"#ffffff\"><b>I’ve just installed Yggdrasil and I can’t ping anyone. What have I missed?</b></font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">Yggdrasil requires that you configure either a static peer to another Yggdrasil node, or that you discover another Yggdrasil node on the same subnet using multicast discovery (which is enabled by default). If you have not added or discovered any peers, you will not be able to reach beyond your own node.\n" +
|
||||||
|
"You can check if you have any peers by running yggdrasilctl getPeers - peer on port 0 is your own node, ports 1 and above are your active peers.\n" +
|
||||||
|
"Stuck for peers? Try adding a <a href=\"https://github.com/yggdrasil-network/public-peers\"><u>public peer</u></a>.\n" +
|
||||||
|
"</font></p>\n" +
|
||||||
|
"\n" +
|
||||||
|
"<font class=\"western\" style=\"font-size: 14pt\" color=\"#ffffff\"><b>I’ve installed the Yggdrasil Debian package and now I can’t find the logs.</b></font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">The Debian package installs the Yggdrasil service into systemd, therefore you can query systemd for the logs:\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">\n" +
|
||||||
|
" systemctl status yggdrasil\n" +
|
||||||
|
"</font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">\n" +
|
||||||
|
" journalctl -u yggdrasil\n" +
|
||||||
|
"</font<\n" +
|
||||||
|
"</font></p>\n" +
|
||||||
|
"\n" +
|
||||||
|
"<font class=\"western\" style=\"font-size: 14pt\" color=\"#ffffff\"><b>I’ve modified the configuration file but nothing has changed.</b></font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">Yggdrasil only loads the configuration at startup. Restart the Yggdrasil process or service to load the new configuration.\n" +
|
||||||
|
"</font></p>\n" +
|
||||||
|
"\n" +
|
||||||
|
"<font class=\"western\" style=\"font-size: 14pt\" color=\"#ffffff\"><b>I’m running Yggdrasil on a machine that is directly reachable from the Internet. Does this mean anyone can peer with me?</b></font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">Without any further configuration, yes.\n" +
|
||||||
|
"However, you can either limit incoming connections to your host using a firewall by limiting or denying connections to the port specified in your Listen configuration option. This is useful if you want to limit peerings from certain IP ranges or on certain interfaces.\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">\n" +
|
||||||
|
"Alternatively, you can limit who can peer with you by modifying the AllowedEncryptionPublicKeys option in your Yggdrasil configuration. When this list is empty, any remote node is allowed to peer with you.\n" +
|
||||||
|
"</font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">\n" +
|
||||||
|
"To restrict incoming peerings to certain nodes, you should first ask the operators of those nodes for their EncryptionPublicKey and then add those public keys into your own AllowedEncryptionPublicKeys list. From that point forward, only nodes with those public keys will be allowed to peer with you.\n" +
|
||||||
|
"</font>\n" +
|
||||||
|
"</font></p>\n" +
|
||||||
|
"\n" +
|
||||||
|
"<font class=\"western\" style=\"font-size: 14pt\" color=\"#ffffff\"><b>I am running Yggdrasil from behind a NAT. Will this affect my connectivity?</b></font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">To accept incoming peerings, you will probably need to configure port forwarding on your router/gateway. Yggdrasil listens on the port number specified in the Listen setting, so forward this port to the machine that runs Yggdrasil.\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">\n" +
|
||||||
|
"To use outbound peerings, that is, static peers that have been configured in your Peers setting, you will likely not need to change anything.\n" +
|
||||||
|
"</font>\n" +
|
||||||
|
"</font></p>\n" +
|
||||||
|
"\n" +
|
||||||
|
"<font class=\"western\" style=\"font-size: 14pt\" color=\"#ffffff\"><b>Why does my Yggdrasil adapter have an unusually high MTU?</b></font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">Yggdrasil peerings are typically stream-based and therefore don’t suffer from fragmentation issues when pushing large amounts of data. By using the largest possible MTU supported by a platform, we can send much more data in each write, and the TCP connection will take care of the rest. This also helps somewhat in the reduction of TCP-over-TCP amplification, as there are less control messages to be amplified.\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">\n" +
|
||||||
|
"This also uses less CPU, as we can send more data for every system call on the TUN/TAP adapter or network socket. System calls often result in context switches by the operating system and are expensive operations, therefore by using an MTU of up to 65535, we can save as many as 42 context switches for each packet - a substantial performance improvement!\n" +
|
||||||
|
"</font>\n" +
|
||||||
|
"</font></p>\n" +
|
||||||
|
"\n" +
|
||||||
|
"<font class=\"western\" style=\"font-size: 14pt\" color=\"#ffffff\"><b>I’ve changed my AdminListen port and now yggdrasilctl doesn’t work.</b></font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">yggdrasilctl will assume that your admin port is on localhost:9001. If you have changed it, simply pass your configured endpoint through to yggdrasilctl, i.e.\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">\n" +
|
||||||
|
"yggdrasilctl -endpoint=127.0.0.1:12345\n" +
|
||||||
|
"</font>\n" +
|
||||||
|
"</font></p>\n" +
|
||||||
|
"\n" +
|
||||||
|
"<font class=\"western\" style=\"font-size: 14pt\" color=\"#ffffff\"><b>I want to run an Yggdrasil router to provide connectivity for other people, but I don’t want them to be able to reach my own machine.</b></font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">You can set the IfName configuration setting to \"none\". This will load Yggdrasil, but will not create a TUN/TAP adapter, meaning that your host will not be exposed to the Yggdrasil network. This does of course mean that you won’t be able to send any traffic from that node to Yggdrasil either\n" +
|
||||||
|
"</font></p>\n" +
|
||||||
|
"\n" +
|
||||||
|
"<font class=\"western\" style=\"font-size: 14pt\" color=\"#ffffff\"><b>Does Yggdrasil work alongside an existing VPN?</b></font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">Yes, Yggdrasil should not interfere with existing VPNs. VPN traffic can be sent while using Yggdrasil.\n" +
|
||||||
|
"</font></p>\n" +
|
||||||
|
"\n" +
|
||||||
|
"<font class=\"western\" style=\"font-size: 14pt\" color=\"#ffffff\"><b>Does Yggdrasil work with networks like Tor or I2P?</b></font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">Yggdrasil can peer over Tor or I2P. See <a href=\"https://github.com/yggdrasil-network/public-peers/tree/master/other\"><u>/public-peers/tree/master/other</u></a> for public peers.\n" +
|
||||||
|
"</font></p>\n" +
|
||||||
|
"\n" +
|
||||||
|
"<font class=\"western\" style=\"font-size: 14pt\" color=\"#ffffff\"><b>I want to allow outgoing connections from my machine but prevent unwanted incoming connections.</b></font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">Generally this requires you to use a firewall. The steps for this will vary from platform to platform.\n" +
|
||||||
|
"</font></p>\n" +
|
||||||
|
"<font class=\"western\" style=\"font-size: 13pt\" color=\"#ffffff\"><b>Linux (with ip6tables)</b></font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">Assuming your TUN/TAP adapter is tun0:\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">\n" +
|
||||||
|
"ip6tables -A INPUT -i tun0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT\n" +
|
||||||
|
"</font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">\n" +
|
||||||
|
"ip6tables -A INPUT -i tun0 -m conntrack --ctstate INVALID -j DROP\n" +
|
||||||
|
"</font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">\n" +
|
||||||
|
"ip6tables -A INPUT -i tun0 -j DROP\n" +
|
||||||
|
"</font>\n" +
|
||||||
|
"</font></p>\n" +
|
||||||
|
"\n" +
|
||||||
|
"<font class=\"western\" style=\"font-size: 13pt\" color=\"#ffffff\"><b>Windows (with Windows Firewall)</b></font>\n" +
|
||||||
|
"\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">Windows, by default, will classify the TAP adapter as a “Public Network”. Configure Windows Firewall to prevent incoming connections on Public networks.\n" +
|
||||||
|
"</font></p>\n" +
|
||||||
|
"\n" +
|
||||||
|
"<font class=\"western\" style=\"font-size: 13pt\" color=\"#ffffff\"><b>macOS (with built-in firewall)</b></font>\n" +
|
||||||
|
"\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">macOS has an application firewall, therefore any firewall policies applied on other interfaces will also apply to the Yggdrasil interface.\n" +
|
||||||
|
"</font></p>\n" +
|
||||||
|
"\n" +
|
||||||
|
"<font class=\"western\" style=\"font-size: 14pt\" color=\"#ffffff\"><b>Is there any benefit to being the “root node” of the network?</b></font>\n" +
|
||||||
|
"<p>\n" +
|
||||||
|
"<font style=\"font-size: 10pt\" color=\"#ffffff\">No. At worst, the root node may be used in worst-case-scenario paths between other nodes in the absence of being able to determine better routes, but this is not advantageous.\n" +
|
||||||
|
"</font></p>\n" +
|
||||||
|
"</body>"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_about)
|
||||||
|
setSupportActionBar(findViewById(R.id.toolbar))
|
||||||
|
var textArea = this.findViewById<TextView>(R.id.about)
|
||||||
|
textArea.movementMethod = LinkMovementMethod.getInstance();
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
|
||||||
|
textArea.text = Html.fromHtml(about, HtmlCompat.FROM_HTML_MODE_LEGACY)
|
||||||
|
} else {
|
||||||
|
textArea.text = Html.fromHtml(about)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package io.github.chronosx88.yggdrasil
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.widget.ListView
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import io.github.chronosx88.yggdrasil.models.NodeInfo
|
||||||
|
import io.github.chronosx88.yggdrasil.models.config.CopyInfoAdapter
|
||||||
|
import io.github.chronosx88.yggdrasil.models.config.SelectDNSInfoListAdapter
|
||||||
|
|
||||||
|
class CopyLocalNodeInfoActivity: AppCompatActivity() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_copy_local_node_info)
|
||||||
|
setSupportActionBar(findViewById(R.id.toolbar))
|
||||||
|
val preferences =
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(this.baseContext)
|
||||||
|
val ipv6Address = intent.extras!!.getString(MainActivity.IPv6, "")
|
||||||
|
val signingPublicKey = preferences.getString(MainActivity.signingPublicKey, "")
|
||||||
|
val encryptionPublicKey = preferences.getString(MainActivity.encryptionPublicKey, "")
|
||||||
|
var nodeInfoListView = findViewById<ListView>(R.id.nodeInfoList)
|
||||||
|
val nodeInfoList = listOf<NodeInfo>(NodeInfo("IP address", ipv6Address!!), NodeInfo("Encryption Public Key", encryptionPublicKey!!), NodeInfo("Signing Public Key", signingPublicKey!!));
|
||||||
|
var adapter = CopyInfoAdapter(this, nodeInfoList)
|
||||||
|
nodeInfoListView.adapter = adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -59,11 +59,11 @@ class DNSListActivity : AppCompatActivity() {
|
|||||||
try {
|
try {
|
||||||
|
|
||||||
for (d in cd) {
|
for (d in cd) {
|
||||||
var ping = ping(d.address, 53)
|
var ping = ping(d.address.hostAddress, 53)
|
||||||
d.ping = ping
|
d.ping = ping
|
||||||
}
|
}
|
||||||
for (dns in allDNS) {
|
for (dns in allDNS) {
|
||||||
var ping = ping(dns.address, 53)
|
var ping = ping(dns.address.hostAddress, 53)
|
||||||
dns.ping = ping
|
dns.ping = ping
|
||||||
runOnUiThread(
|
runOnUiThread(
|
||||||
Runnable
|
Runnable
|
||||||
@ -107,16 +107,16 @@ class DNSListActivity : AppCompatActivity() {
|
|||||||
var ccpInput = view.findViewById<com.hbb20.CountryCodePicker>(R.id.ccp)
|
var ccpInput = view.findViewById<com.hbb20.CountryCodePicker>(R.id.ccp)
|
||||||
var ip = ipInput.text.toString().toLowerCase()
|
var ip = ipInput.text.toString().toLowerCase()
|
||||||
var ccp = ccpInput.selectedCountryNameCode
|
var ccp = ccpInput.selectedCountryNameCode
|
||||||
GlobalScope.launch {
|
thread(start = true) {
|
||||||
var di = DNSInfo(InetAddress.getByName("["+ip+"]"), ccp, "User DNS")
|
var di = DNSInfo(InetAddress.getByName("["+ip+"]"), ccp, "User DNS")
|
||||||
try {
|
try {
|
||||||
var ping = ping(di.address, 53)
|
var ping = ping(di.address.hostAddress, 53)
|
||||||
di.ping = ping
|
di.ping = ping
|
||||||
} catch(e: Throwable){
|
} catch(e: Throwable){
|
||||||
di.ping = Int.MAX_VALUE
|
di.ping = Int.MAX_VALUE
|
||||||
}
|
}
|
||||||
withContext(Dispatchers.Main) {
|
runOnUiThread {
|
||||||
var selectAdapter = (findViewById<ListView>(R.id.peerList).adapter as SelectDNSInfoListAdapter)
|
var selectAdapter = (findViewById<ListView>(R.id.dnsList).adapter as SelectDNSInfoListAdapter)
|
||||||
selectAdapter.addItem(0, di)
|
selectAdapter.addItem(0, di)
|
||||||
selectAdapter.notifyDataSetChanged()
|
selectAdapter.notifyDataSetChanged()
|
||||||
ad.dismiss()
|
ad.dismiss()
|
||||||
@ -127,7 +127,7 @@ class DNSListActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
// Inflate the menu; this adds items to the action bar if it is present.
|
// Inflate the menu; this adds items to the action bar if it is present.
|
||||||
menuInflater.inflate(R.menu.save, menu)
|
menuInflater.inflate(R.menu.save_dns, menu)
|
||||||
val item = menu.findItem(R.id.saveItem) as MenuItem
|
val item = menu.findItem(R.id.saveItem) as MenuItem
|
||||||
item.setActionView(R.layout.menu_save)
|
item.setActionView(R.layout.menu_save)
|
||||||
val saveButton = item
|
val saveButton = item
|
||||||
|
@ -4,13 +4,16 @@ import android.app.Activity
|
|||||||
import android.app.ActivityManager
|
import android.app.ActivityManager
|
||||||
import android.content.*
|
import android.content.*
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.appcompat.widget.SwitchCompat
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import dalvik.system.DexFile
|
||||||
import io.github.chronosx88.yggdrasil.models.DNSInfo
|
import io.github.chronosx88.yggdrasil.models.DNSInfo
|
||||||
import io.github.chronosx88.yggdrasil.models.PeerInfo
|
import io.github.chronosx88.yggdrasil.models.PeerInfo
|
||||||
import io.github.chronosx88.yggdrasil.models.config.DNSInfoListAdapter
|
import io.github.chronosx88.yggdrasil.models.config.DNSInfoListAdapter
|
||||||
@ -22,11 +25,6 @@ import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.deserializeS
|
|||||||
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.deserializeStringSet2PeerInfoSet
|
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.deserializeStringSet2PeerInfoSet
|
||||||
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.serializeDNSInfoSet2StringList
|
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.serializeDNSInfoSet2StringList
|
||||||
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.serializePeerInfoSet2StringList
|
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.serializePeerInfoSet2StringList
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import java.net.InetAddress
|
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
|
|
||||||
@ -57,24 +55,28 @@ class MainActivity : AppCompatActivity() {
|
|||||||
const val CURRENT_PEERS = "CURRENT_PEERS_v1.2.1"
|
const val CURRENT_PEERS = "CURRENT_PEERS_v1.2.1"
|
||||||
const val CURRENT_DNS = "CURRENT_DNS_v1.2"
|
const val CURRENT_DNS = "CURRENT_DNS_v1.2"
|
||||||
const val START_VPN = "START_VPN"
|
const val START_VPN = "START_VPN"
|
||||||
private const val TAG="Yggdrasil"
|
private const val TAG = "Yggdrasil"
|
||||||
private const val VPN_REQUEST_CODE = 0x0F
|
private const val VPN_REQUEST_CODE = 0x0F
|
||||||
|
|
||||||
@JvmStatic var isStarted = false
|
@JvmStatic
|
||||||
@JvmStatic var isCancelled = false
|
var isStarted = false
|
||||||
@JvmStatic var address = ""
|
@JvmStatic
|
||||||
|
var isCancelled = false
|
||||||
|
@JvmStatic
|
||||||
|
var address: String? = ""
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var currentPeers = setOf<PeerInfo>()
|
private var currentPeers = setOf<PeerInfo>()
|
||||||
private var currentDNS = setOf<DNSInfo>()
|
private var currentDNS = setOf<DNSInfo>()
|
||||||
private var meshPeersReceiver: BroadcastReceiver? = null
|
private var networkStateReceiver: BroadcastReceiver? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
setSupportActionBar(findViewById(R.id.toolbar))
|
setSupportActionBar(findViewById(R.id.toolbar))
|
||||||
isStarted = isYggServiceRunning(this)
|
isStarted = isYggServiceRunning(this)
|
||||||
val switchOn = findViewById<Switch>(R.id.switchOn)
|
val switchOn = findViewById<SwitchCompat>(R.id.switchOn)
|
||||||
switchOn.isChecked = isStarted
|
switchOn.isChecked = isStarted
|
||||||
|
|
||||||
switchOn.setOnCheckedChangeListener { _, isChecked ->
|
switchOn.setOnCheckedChangeListener { _, isChecked ->
|
||||||
@ -92,27 +94,34 @@ class MainActivity : AppCompatActivity() {
|
|||||||
//save to shared preferences
|
//save to shared preferences
|
||||||
val preferences =
|
val preferences =
|
||||||
PreferenceManager.getDefaultSharedPreferences(this.baseContext)
|
PreferenceManager.getDefaultSharedPreferences(this.baseContext)
|
||||||
val staticIP = findViewById<Switch>(R.id.staticIP)
|
val staticIP = findViewById<SwitchCompat>(R.id.staticIP)
|
||||||
staticIP.isChecked =
|
staticIP.isChecked =
|
||||||
preferences.getString(STATIC_IP, null) != null
|
preferences.getString(STATIC_IP, null) != null
|
||||||
val peersListView = findViewById<ListView>(R.id.peers)
|
val peersListView = findViewById<ListView>(R.id.peers)
|
||||||
|
|
||||||
currentPeers = deserializeStringSet2PeerInfoSet(preferences.getStringSet(CURRENT_PEERS, HashSet())!!)
|
currentPeers = deserializeStringSet2PeerInfoSet(
|
||||||
|
preferences.getStringSet(
|
||||||
|
CURRENT_PEERS,
|
||||||
|
HashSet()
|
||||||
|
)!!
|
||||||
|
)
|
||||||
val adapter = PeerInfoListAdapter(this, currentPeers.sortedWith(compareBy { it.ping }))
|
val adapter = PeerInfoListAdapter(this, currentPeers.sortedWith(compareBy { it.ping }))
|
||||||
peersListView.adapter = adapter
|
peersListView.adapter = adapter
|
||||||
|
|
||||||
|
if (adapter.count > 10) {
|
||||||
|
val item = adapter.getView(0, null, peersListView)
|
||||||
|
item.measure(0, 0)
|
||||||
|
val params = LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
(10 * item.measuredHeight).toInt()
|
||||||
|
)
|
||||||
|
peersListView.layoutParams = params
|
||||||
|
}
|
||||||
|
|
||||||
if(isStarted && this.currentPeers.isEmpty()) {
|
if(isStarted && this.currentPeers.isEmpty()) {
|
||||||
updatePeers()
|
updatePeers()
|
||||||
}
|
}
|
||||||
val copyAddressButton = findViewById<Button>(R.id.copyIp)
|
|
||||||
copyAddressButton.setOnClickListener {
|
|
||||||
val ip = findViewById<TextView>(R.id.ip)
|
|
||||||
val clipboard: ClipboardManager =
|
|
||||||
getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
|
||||||
val clip =
|
|
||||||
ClipData.newPlainText("IP address", ip.text.toString())
|
|
||||||
clipboard.setPrimaryClip(clip)
|
|
||||||
showToast(getString(R.string.address_copied))
|
|
||||||
}
|
|
||||||
val editPeersButton = findViewById<Button>(R.id.edit)
|
val editPeersButton = findViewById<Button>(R.id.edit)
|
||||||
editPeersButton.setOnClickListener {
|
editPeersButton.setOnClickListener {
|
||||||
if(isStarted){
|
if(isStarted){
|
||||||
@ -125,7 +134,12 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val listViewDNS = findViewById<ListView>(R.id.dns)
|
val listViewDNS = findViewById<ListView>(R.id.dns)
|
||||||
currentDNS = deserializeStringSet2DNSInfoSet(preferences.getStringSet(CURRENT_DNS, HashSet())!!)
|
currentDNS = deserializeStringSet2DNSInfoSet(
|
||||||
|
preferences.getStringSet(
|
||||||
|
CURRENT_DNS,
|
||||||
|
HashSet()
|
||||||
|
)!!
|
||||||
|
)
|
||||||
val adapterDns = DNSInfoListAdapter(this, currentDNS.sortedWith(compareBy { it.ping }))
|
val adapterDns = DNSInfoListAdapter(this, currentDNS.sortedWith(compareBy { it.ping }))
|
||||||
listViewDNS.adapter = adapterDns
|
listViewDNS.adapter = adapterDns
|
||||||
val editDnsButton = findViewById<Button>(R.id.editDNS)
|
val editDnsButton = findViewById<Button>(R.id.editDNS)
|
||||||
@ -138,16 +152,79 @@ class MainActivity : AppCompatActivity() {
|
|||||||
intent.putStringArrayListExtra(DNS_LIST, serializeDNSInfoSet2StringList(currentDNS))
|
intent.putStringArrayListExtra(DNS_LIST, serializeDNSInfoSet2StringList(currentDNS))
|
||||||
startActivityForResult(intent, DNS_LIST_CODE)
|
startActivityForResult(intent, DNS_LIST_CODE)
|
||||||
}
|
}
|
||||||
|
val nodeInfoButton = findViewById<Button>(R.id.nodeInfo)
|
||||||
|
nodeInfoButton.setOnClickListener {
|
||||||
|
if(isStarted) {
|
||||||
|
val intent = Intent(this@MainActivity, CopyLocalNodeInfoActivity::class.java)
|
||||||
|
intent.putExtra(IPv6, findViewById<TextView>(R.id.ip).text.toString())
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
if(isStarted){
|
if(isStarted){
|
||||||
val ipLayout = findViewById<LinearLayout>(R.id.ipLayout)
|
val ipLayout = findViewById<LinearLayout>(R.id.ipLayout)
|
||||||
ipLayout.visibility = View.VISIBLE
|
ipLayout.visibility = View.VISIBLE
|
||||||
findViewById<TextView>(R.id.ip).text = address
|
findViewById<TextView>(R.id.ip).text = address
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
val connectivityManager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
|
connectivityManager?.let {
|
||||||
|
it.registerDefaultNetworkCallback(object : ConnectivityManager.NetworkCallback() {
|
||||||
|
override fun onAvailable(network: Network) {
|
||||||
|
if(isStarted) {
|
||||||
|
stopVpn()
|
||||||
|
Thread.sleep(1000)
|
||||||
|
startVpn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun onLost(network: Network?) {
|
||||||
|
if(isStarted) {
|
||||||
|
stopVpn()
|
||||||
|
Thread.sleep(1000)
|
||||||
|
startVpn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
networkStateReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
val status: Int = NetworkUtils.getConnectivityStatusString(context)
|
||||||
|
Log.i(TAG, "Network state has been changed")
|
||||||
|
if ("android.net.conn.CONNECTIVITY_CHANGE" == intent.action) {
|
||||||
|
if (status == NetworkUtils.NETWORK_STATUS_NOT_CONNECTED) {
|
||||||
|
if(isStarted) {
|
||||||
|
stopVpn()
|
||||||
|
Thread.sleep(1000)
|
||||||
|
startVpn()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(isStarted) {
|
||||||
|
stopVpn()
|
||||||
|
Thread.sleep(1000)
|
||||||
|
startVpn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
val sourceDir: String = this.applicationInfo.sourceDir
|
||||||
|
val dexFile = DexFile(sourceDir)
|
||||||
|
val cl = classLoader
|
||||||
|
val c: Class<*> = dexFile.loadClass("dummy/Dummy", cl)
|
||||||
|
}
|
||||||
|
val versionName = findViewById<Button>(R.id.about)
|
||||||
|
versionName.text = """version:${BuildConfig.VERSION_NAME} build:${BuildConfig.VERSION_CODE}"""
|
||||||
|
versionName.setOnClickListener {
|
||||||
|
val intent = Intent(this@MainActivity, AboutActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stopVpn(){
|
private fun stopVpn(){
|
||||||
Log.d(TAG,"Stop")
|
Log.i(TAG, "Stop")
|
||||||
val intent = Intent(this, YggdrasilTunService::class.java)
|
val intent = Intent(this, YggdrasilTunService::class.java)
|
||||||
val TASK_CODE = 100
|
val TASK_CODE = 100
|
||||||
val pi = createPendingResult(TASK_CODE, intent, 0)
|
val pi = createPendingResult(TASK_CODE, intent, 0)
|
||||||
@ -157,7 +234,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun startVpn(){
|
private fun startVpn(){
|
||||||
Log.d(TAG,"Start")
|
Log.i(TAG, "Start")
|
||||||
val intent= VpnService.prepare(this)
|
val intent= VpnService.prepare(this)
|
||||||
if (intent!=null){
|
if (intent!=null){
|
||||||
startActivityForResult(intent, VPN_REQUEST_CODE)
|
startActivityForResult(intent, VPN_REQUEST_CODE)
|
||||||
@ -167,7 +244,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateDNS(){
|
private fun updateDNS(){
|
||||||
Log.d(TAG,"Update DNS")
|
Log.i(TAG, "Update DNS")
|
||||||
val intent = Intent(this, YggdrasilTunService::class.java)
|
val intent = Intent(this, YggdrasilTunService::class.java)
|
||||||
val TASK_CODE = 100
|
val TASK_CODE = 100
|
||||||
val pi = createPendingResult(TASK_CODE, intent, 0)
|
val pi = createPendingResult(TASK_CODE, intent, 0)
|
||||||
@ -178,7 +255,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updatePeers(){
|
private fun updatePeers(){
|
||||||
Log.d(TAG,"Update Peers")
|
Log.d(TAG, "Update Peers")
|
||||||
val intent = Intent(this, YggdrasilTunService::class.java)
|
val intent = Intent(this, YggdrasilTunService::class.java)
|
||||||
val TASK_CODE = 100
|
val TASK_CODE = 100
|
||||||
val pi = createPendingResult(TASK_CODE, intent, 0)
|
val pi = createPendingResult(TASK_CODE, intent, 0)
|
||||||
@ -209,9 +286,13 @@ class MainActivity : AppCompatActivity() {
|
|||||||
val pi = createPendingResult(TASK_CODE, intent, 0)
|
val pi = createPendingResult(TASK_CODE, intent, 0)
|
||||||
intent.putExtra(PARAM_PINTENT, pi)
|
intent.putExtra(PARAM_PINTENT, pi)
|
||||||
intent.putExtra(COMMAND, START)
|
intent.putExtra(COMMAND, START)
|
||||||
intent.putStringArrayListExtra(CURRENT_PEERS, serializePeerInfoSet2StringList(currentPeers))
|
intent.putStringArrayListExtra(
|
||||||
|
CURRENT_PEERS, serializePeerInfoSet2StringList(
|
||||||
|
currentPeers
|
||||||
|
)
|
||||||
|
)
|
||||||
intent.putStringArrayListExtra(CURRENT_DNS, serializeDNSInfoSet2StringList(currentDNS))
|
intent.putStringArrayListExtra(CURRENT_DNS, serializeDNSInfoSet2StringList(currentDNS))
|
||||||
intent.putExtra(STATIC_IP, findViewById<Switch>(R.id.staticIP).isChecked)
|
intent.putExtra(STATIC_IP, findViewById<SwitchCompat>(R.id.staticIP).isChecked)
|
||||||
|
|
||||||
startService(intent)
|
startService(intent)
|
||||||
}
|
}
|
||||||
@ -223,14 +304,17 @@ class MainActivity : AppCompatActivity() {
|
|||||||
var currentPeers = data.extras!!.getStringArrayList(PEER_LIST)
|
var currentPeers = data.extras!!.getStringArrayList(PEER_LIST)
|
||||||
/*WiFi Direct test. need peer empty list*/
|
/*WiFi Direct test. need peer empty list*/
|
||||||
this.currentPeers = deserializeStringList2PeerInfoSet(currentPeers)
|
this.currentPeers = deserializeStringList2PeerInfoSet(currentPeers)
|
||||||
val adapter = PeerInfoListAdapter(this, this.currentPeers.sortedWith(compareBy { it.ping }))
|
val adapter = PeerInfoListAdapter(
|
||||||
|
this,
|
||||||
|
this.currentPeers.sortedWith(compareBy { it.ping })
|
||||||
|
)
|
||||||
val listView = findViewById<ListView>(R.id.peers)
|
val listView = findViewById<ListView>(R.id.peers)
|
||||||
listView.adapter = adapter
|
listView.adapter = adapter
|
||||||
|
|
||||||
//save to shared preferences
|
//save to shared preferences
|
||||||
val preferences =
|
val preferences =
|
||||||
PreferenceManager.getDefaultSharedPreferences(this.baseContext)
|
PreferenceManager.getDefaultSharedPreferences(this.baseContext)
|
||||||
preferences.edit().putStringSet(CURRENT_PEERS, HashSet(currentPeers)).apply()
|
preferences.edit().putStringSet(CURRENT_PEERS, currentPeers!!.toHashSet()).apply()
|
||||||
if(isStarted){
|
if(isStarted){
|
||||||
//TODO implement UpdateConfig method in native interface and apply peer changes
|
//TODO implement UpdateConfig method in native interface and apply peer changes
|
||||||
stopVpn()
|
stopVpn()
|
||||||
@ -248,13 +332,16 @@ class MainActivity : AppCompatActivity() {
|
|||||||
if(data!!.extras!=null){
|
if(data!!.extras!=null){
|
||||||
var currentDNS = data.extras!!.getStringArrayList(DNS_LIST)
|
var currentDNS = data.extras!!.getStringArrayList(DNS_LIST)
|
||||||
this.currentDNS = deserializeStringList2DNSInfoSet(currentDNS)
|
this.currentDNS = deserializeStringList2DNSInfoSet(currentDNS)
|
||||||
val adapter = DNSInfoListAdapter(this, this.currentDNS.sortedWith(compareBy { it.ping }))
|
val adapter = DNSInfoListAdapter(
|
||||||
|
this,
|
||||||
|
this.currentDNS.sortedWith(compareBy { it.ping })
|
||||||
|
)
|
||||||
val listView = findViewById<ListView>(R.id.dns)
|
val listView = findViewById<ListView>(R.id.dns)
|
||||||
listView.adapter = adapter
|
listView.adapter = adapter
|
||||||
//save to shared preferences
|
//save to shared preferences
|
||||||
val preferences =
|
val preferences =
|
||||||
PreferenceManager.getDefaultSharedPreferences(this.baseContext)
|
PreferenceManager.getDefaultSharedPreferences(this.baseContext)
|
||||||
preferences.edit().putStringSet(CURRENT_DNS, HashSet(currentDNS)).apply()
|
preferences.edit().putStringSet(CURRENT_DNS, currentDNS!!.toHashSet()).apply()
|
||||||
if(isStarted){
|
if(isStarted){
|
||||||
updateDNS()
|
updateDNS()
|
||||||
}
|
}
|
||||||
@ -264,7 +351,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
when (resultCode) {
|
when (resultCode) {
|
||||||
STATUS_START -> {
|
STATUS_START -> {
|
||||||
print("service started")
|
print("service started")
|
||||||
if(this.currentPeers.isEmpty()){
|
if (this.currentPeers.isEmpty()) {
|
||||||
checkPeers()
|
checkPeers()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -280,8 +367,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
val ipLayout = findViewById<LinearLayout>(R.id.ipLayout)
|
val ipLayout = findViewById<LinearLayout>(R.id.ipLayout)
|
||||||
ipLayout.visibility = View.GONE
|
ipLayout.visibility = View.GONE
|
||||||
}
|
}
|
||||||
STATUS_PEERS_UPDATE ->{
|
STATUS_PEERS_UPDATE -> {
|
||||||
if(data!!.extras!=null) {
|
if (data!!.extras != null) {
|
||||||
thread(start = true) {
|
thread(start = true) {
|
||||||
val meshPeers = deserializePeerStringList2PeerInfoSet(
|
val meshPeers = deserializePeerStringList2PeerInfoSet(
|
||||||
data.extras!!.getStringArrayList(MESH_PEERS)
|
data.extras!!.getStringArrayList(MESH_PEERS)
|
||||||
@ -289,7 +376,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
val listView = findViewById<ListView>(R.id.peers)
|
val listView = findViewById<ListView>(R.id.peers)
|
||||||
val adapter = PeerInfoListAdapter(
|
val adapter = PeerInfoListAdapter(
|
||||||
this@MainActivity,
|
this@MainActivity,
|
||||||
meshPeers.filter { it.schema!="self" }.sortedWith(compareBy { it.ping })
|
meshPeers.filter { it.schema != "self" }
|
||||||
|
.sortedWith(compareBy { it.ping })
|
||||||
)
|
)
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
listView.adapter = adapter
|
listView.adapter = adapter
|
||||||
@ -326,14 +414,14 @@ class MainActivity : AppCompatActivity() {
|
|||||||
super.onRestoreInstanceState(savedInstanceState)
|
super.onRestoreInstanceState(savedInstanceState)
|
||||||
val preferences =
|
val preferences =
|
||||||
PreferenceManager.getDefaultSharedPreferences(this.baseContext)
|
PreferenceManager.getDefaultSharedPreferences(this.baseContext)
|
||||||
findViewById<Switch>(R.id.staticIP).isChecked =
|
findViewById<SwitchCompat>(R.id.staticIP).isChecked =
|
||||||
preferences.getString(STATIC_IP, null) != null
|
preferences.getString(STATIC_IP, null) != null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
if (meshPeersReceiver != null){
|
if (networkStateReceiver != null){
|
||||||
unregisterReceiver(meshPeersReceiver);
|
unregisterReceiver(networkStateReceiver);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,21 +1,26 @@
|
|||||||
package io.github.chronosx88.yggdrasil
|
package io.github.chronosx88.yggdrasil
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.*
|
import android.view.*
|
||||||
|
import android.webkit.URLUtil
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.ListView
|
import android.widget.ListView
|
||||||
import android.widget.PopupWindow
|
import android.widget.PopupWindow
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import com.hbb20.BuildConfig
|
||||||
import com.hbb20.CCPCountry
|
import com.hbb20.CCPCountry
|
||||||
|
import com.vincentbrison.openlibraries.android.dualcache.Builder
|
||||||
|
import com.vincentbrison.openlibraries.android.dualcache.SizeOf
|
||||||
|
import com.vincentbrison.openlibraries.android.dualcache.JsonSerializer
|
||||||
import io.github.chronosx88.yggdrasil.models.PeerInfo
|
import io.github.chronosx88.yggdrasil.models.PeerInfo
|
||||||
import io.github.chronosx88.yggdrasil.models.Status
|
import io.github.chronosx88.yggdrasil.models.Status
|
||||||
import io.github.chronosx88.yggdrasil.models.config.DropDownAdapter
|
import io.github.chronosx88.yggdrasil.models.config.DropDownAdapter
|
||||||
@ -23,23 +28,28 @@ import io.github.chronosx88.yggdrasil.models.config.SelectPeerInfoListAdapter
|
|||||||
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.deserializeStringList2PeerInfoSet
|
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.deserializeStringList2PeerInfoSet
|
||||||
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.ping
|
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.ping
|
||||||
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.serializePeerInfoSet2StringList
|
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.serializePeerInfoSet2StringList
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
import java.net.UnknownHostException
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
|
|
||||||
|
|
||||||
class PeerListActivity : AppCompatActivity() {
|
class PeerListActivity : AppCompatActivity() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
const val PEER_LIST = "PEER_LIST"
|
||||||
const val PEER_LIST_URL = "https://publicpeers.neilalexander.dev/publicnodes.json"
|
const val PEER_LIST_URL = "https://publicpeers.neilalexander.dev/publicnodes.json"
|
||||||
|
const val CACHE_NAME = "PEER_LIST_CACHE"
|
||||||
|
const val ONLINE_PEERINFO_LIST = "online_peer_info_list"
|
||||||
|
const val OFFLINE_PEERINFO_LIST = "offline_peer_info_list"
|
||||||
|
const val TEST_APP_VERSION = BuildConfig.VERSION_CODE
|
||||||
|
const val RAM_MAX_SIZE = 100000
|
||||||
|
const val DISK_MAX_SIZE = 100000
|
||||||
}
|
}
|
||||||
|
|
||||||
fun downloadJson(link: String): String {
|
fun downloadJson(link: String): String {
|
||||||
@ -52,8 +62,8 @@ class PeerListActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var isLoading = true;
|
private var peerListUrl = PEER_LIST_URL
|
||||||
|
private var peerListPing = true
|
||||||
var popup: PopupWindow? = null
|
var popup: PopupWindow? = null
|
||||||
var adapter: DropDownAdapter? = null
|
var adapter: DropDownAdapter? = null
|
||||||
|
|
||||||
@ -64,26 +74,57 @@ class PeerListActivity : AppCompatActivity() {
|
|||||||
findViewById<FloatingActionButton>(R.id.fab).setOnClickListener { _ ->
|
findViewById<FloatingActionButton>(R.id.fab).setOnClickListener { _ ->
|
||||||
addNewPeer()
|
addNewPeer()
|
||||||
}
|
}
|
||||||
|
val preferences =
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(this.baseContext)
|
||||||
|
var peerListUrl: String =
|
||||||
|
preferences.getString(PEER_LIST, "")!!
|
||||||
|
if(!peerListUrl.isNullOrBlank()){
|
||||||
|
this@PeerListActivity.peerListUrl = peerListUrl
|
||||||
|
}
|
||||||
var extras = intent.extras
|
var extras = intent.extras
|
||||||
var peerList = findViewById<ListView>(R.id.peerList)
|
var peerList = findViewById<ListView>(R.id.peerList)
|
||||||
var adapter = SelectPeerInfoListAdapter(this, arrayListOf(), mutableSetOf())
|
var adapter = SelectPeerInfoListAdapter(this, arrayListOf(), mutableSetOf())
|
||||||
peerList.adapter = adapter
|
peerList.adapter = adapter
|
||||||
|
var peerInfoListCache = Builder<List<PeerInfo>>(CACHE_NAME, TEST_APP_VERSION)
|
||||||
|
.enableLog()
|
||||||
|
.useReferenceInRam(RAM_MAX_SIZE, SizeOfPeerList())
|
||||||
|
.useSerializerInDisk(
|
||||||
|
DISK_MAX_SIZE, true,
|
||||||
|
JsonSerializer(ArrayList<PeerInfo>().javaClass), baseContext
|
||||||
|
).build();
|
||||||
|
|
||||||
GlobalScope.launch {
|
GlobalScope.launch() {
|
||||||
try {
|
try {
|
||||||
var cp = deserializeStringList2PeerInfoSet(
|
var cp = deserializeStringList2PeerInfoSet(
|
||||||
extras!!.getStringArrayList(MainActivity.PEER_LIST)!!
|
extras!!.getStringArrayList(MainActivity.PEER_LIST)!!
|
||||||
)
|
)
|
||||||
for(pi in cp){
|
for (pi in cp) {
|
||||||
var ping = ping(pi.address, pi.port)
|
var ping = ping(pi.hostName, pi.port)
|
||||||
pi.ping = ping
|
pi.ping = ping
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
var json = downloadJson(PEER_LIST_URL)
|
var peerInfoCache = peerInfoListCache.get(ONLINE_PEERINFO_LIST)
|
||||||
|
if (peerInfoCache != null && peerInfoCache.isNotEmpty()) {
|
||||||
|
for (peerInfo in peerInfoCache) {
|
||||||
|
var ping = ping(peerInfo.hostName, peerInfo.port)
|
||||||
|
peerInfo.ping = ping
|
||||||
|
if (cp.contains(peerInfo)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
adapter.addItem(peerInfo)
|
||||||
|
if (adapter.count % 5 == 0) {
|
||||||
|
adapter.sort()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var json = downloadJson(this@PeerListActivity.peerListUrl)
|
||||||
var countries = CCPCountry.getLibraryMasterCountriesEnglish()
|
var countries = CCPCountry.getLibraryMasterCountriesEnglish()
|
||||||
val mapType: Type = object :
|
val mapType: Type = object :
|
||||||
TypeToken<Map<String?, Map<String, Status>>>() {}.type
|
TypeToken<Map<String?, Map<String, Status>>>() {}.type
|
||||||
val peersMap: Map<String, Map<String, Status>> = Gson().fromJson(json, mapType)
|
val peersMap: Map<String, Map<String, Status>> = Gson().fromJson(json, mapType)
|
||||||
|
var cachePeerInfoList = mutableListOf<PeerInfo>()
|
||||||
for ((country, peers) in peersMap.entries) {
|
for ((country, peers) in peersMap.entries) {
|
||||||
for ((peer, status) in peers) {
|
for ((peer, status) in peers) {
|
||||||
if (status.up) {
|
if (status.up) {
|
||||||
@ -91,45 +132,104 @@ class PeerListActivity : AppCompatActivity() {
|
|||||||
if (ccp.name.toLowerCase()
|
if (ccp.name.toLowerCase()
|
||||||
.contains(country.replace(".md", "").replace("-", " "))
|
.contains(country.replace(".md", "").replace("-", " "))
|
||||||
) {
|
) {
|
||||||
|
if(!peerListPing){
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
var url = URI(peer)
|
var url = URI(peer)
|
||||||
try {
|
try {
|
||||||
var address = InetAddress.getByName(url.host)
|
var address = InetAddress.getByName(url.host)
|
||||||
var peerInfo =
|
var peerInfo =
|
||||||
PeerInfo(url.scheme, address, url.port, ccp.nameCode)
|
PeerInfo(
|
||||||
var ping = ping(address, url.port)
|
url.scheme,
|
||||||
|
address,
|
||||||
|
url.port,
|
||||||
|
ccp.nameCode
|
||||||
|
)
|
||||||
|
var ping = ping(url.host, url.port)
|
||||||
peerInfo.ping = ping
|
peerInfo.ping = ping
|
||||||
if(cp.contains(peerInfo)){
|
if (cp.contains(peerInfo)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (peerInfo.ping < Int.MAX_VALUE) {
|
||||||
|
cachePeerInfoList.add(peerInfo)
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
adapter.addItem(peerInfo)
|
||||||
|
if (adapter.count % 5 == 0) {
|
||||||
|
adapter.sort()
|
||||||
|
if (cachePeerInfoList.size > 0) {
|
||||||
|
peerInfoListCache.put(
|
||||||
|
ONLINE_PEERINFO_LIST,
|
||||||
|
cachePeerInfoList.toList()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
when (e) {
|
||||||
|
is FileNotFoundException, is UnknownHostException -> {
|
||||||
|
var onlinePeerInfoList = peerInfoListCache.get(ONLINE_PEERINFO_LIST)
|
||||||
|
if (onlinePeerInfoList != null) {
|
||||||
|
for (peerInfo in onlinePeerInfoList) {
|
||||||
|
var ping = ping(peerInfo.hostName, peerInfo.port)
|
||||||
|
peerInfo.ping = ping
|
||||||
|
if (cp.contains(peerInfo)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
adapter.addItem(peerInfo)
|
adapter.addItem(peerInfo)
|
||||||
if(adapter.count % 5 == 0) {
|
if (adapter.count % 5 == 0) {
|
||||||
adapter.sort()
|
adapter.sort()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Throwable){
|
}
|
||||||
|
}
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
else -> e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch(e: FileNotFoundException){
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
var currentPeers = ArrayList(cp.sortedWith(compareBy { it.ping }))
|
var currentPeers = ArrayList(cp.sortedWith(compareBy { it.ping }))
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
adapter.addAll(0, currentPeers)
|
adapter.addAll(0, currentPeers)
|
||||||
isLoading = false
|
|
||||||
adapter.setLoading(isLoading)
|
|
||||||
}
|
}
|
||||||
} catch (e: Throwable){
|
} catch (e: Throwable) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun editPeerListUrl() {
|
||||||
|
val view: View = LayoutInflater.from(this).inflate(R.layout.edit_peer_list_url_dialog, null)
|
||||||
|
val ab: AlertDialog.Builder = AlertDialog.Builder(this)
|
||||||
|
ab.setCancelable(true).setView(view)
|
||||||
|
var ad = ab.show()
|
||||||
|
var saveButton = view.findViewById<Button>(R.id.save)
|
||||||
|
var urlInput = view.findViewById<TextView>(R.id.urlInput)
|
||||||
|
urlInput.text = peerListUrl
|
||||||
|
saveButton.setOnClickListener{
|
||||||
|
|
||||||
|
var url = urlInput.text.toString()
|
||||||
|
if(!URLUtil.isValidUrl(url)){
|
||||||
|
urlInput.error = "The URL is invalid!"
|
||||||
|
return@setOnClickListener;
|
||||||
|
}
|
||||||
|
peerListUrl = url
|
||||||
|
val preferences =
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(this.baseContext)
|
||||||
|
preferences.edit().putString(PEER_LIST, peerListUrl).apply()
|
||||||
|
ad.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun addNewPeer() {
|
private fun addNewPeer() {
|
||||||
val view: View = LayoutInflater.from(this).inflate(R.layout.new_peer_dialog, null)
|
val view: View = LayoutInflater.from(this).inflate(R.layout.new_peer_dialog, null)
|
||||||
val countryCode: String = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
val countryCode: String = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
@ -140,7 +240,9 @@ class PeerListActivity : AppCompatActivity() {
|
|||||||
var schemaInput = view.findViewById<TextView>(R.id.schemaInput)
|
var schemaInput = view.findViewById<TextView>(R.id.schemaInput)
|
||||||
var ipInput = view.findViewById<TextView>(R.id.ipInput)
|
var ipInput = view.findViewById<TextView>(R.id.ipInput)
|
||||||
ipInput.requestFocus()
|
ipInput.requestFocus()
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
schemaInput.showSoftInputOnFocus = false
|
schemaInput.showSoftInputOnFocus = false
|
||||||
|
}
|
||||||
schemaInput.setOnFocusChangeListener { v, _ ->
|
schemaInput.setOnFocusChangeListener { v, _ ->
|
||||||
if(schemaInput.isFocused) {
|
if(schemaInput.isFocused) {
|
||||||
onClickSchemaList(v)
|
onClickSchemaList(v)
|
||||||
@ -149,7 +251,11 @@ class PeerListActivity : AppCompatActivity() {
|
|||||||
schemaInput.setOnClickListener { v->
|
schemaInput.setOnClickListener { v->
|
||||||
onClickSchemaList(v)
|
onClickSchemaList(v)
|
||||||
}
|
}
|
||||||
getPopupWindow(R.layout.spinner_item, resources.getStringArray(R.array.schemas), schemaInput);
|
getPopupWindow(
|
||||||
|
R.layout.spinner_item,
|
||||||
|
resources.getStringArray(R.array.schemas),
|
||||||
|
schemaInput
|
||||||
|
);
|
||||||
view.findViewById<com.hbb20.CountryCodePicker>(R.id.ccp).setCountryForNameCode(countryCode)
|
view.findViewById<com.hbb20.CountryCodePicker>(R.id.ccp).setCountryForNameCode(countryCode)
|
||||||
val ab: AlertDialog.Builder = AlertDialog.Builder(this)
|
val ab: AlertDialog.Builder = AlertDialog.Builder(this)
|
||||||
ab.setCancelable(true).setView(view)
|
ab.setCancelable(true).setView(view)
|
||||||
@ -159,15 +265,27 @@ class PeerListActivity : AppCompatActivity() {
|
|||||||
var portInput = view.findViewById<TextView>(R.id.portInput)
|
var portInput = view.findViewById<TextView>(R.id.portInput)
|
||||||
var ccpInput = view.findViewById<com.hbb20.CountryCodePicker>(R.id.ccp)
|
var ccpInput = view.findViewById<com.hbb20.CountryCodePicker>(R.id.ccp)
|
||||||
var schema = schemaInput.text.toString().toLowerCase()
|
var schema = schemaInput.text.toString().toLowerCase()
|
||||||
|
if(schema.isEmpty()){
|
||||||
|
schemaInput.error = "Schema is required"
|
||||||
|
}
|
||||||
var ip = ipInput.text.toString().toLowerCase()
|
var ip = ipInput.text.toString().toLowerCase()
|
||||||
|
if(ip.isEmpty()){
|
||||||
|
ipInput.error = "IP address is required"
|
||||||
|
}
|
||||||
var port = portInput.text.toString().toInt()
|
var port = portInput.text.toString().toInt()
|
||||||
|
if(port<=0){
|
||||||
|
portInput.error = "Port should be > 0"
|
||||||
|
}
|
||||||
|
if(port>=Short.MAX_VALUE){
|
||||||
|
portInput.error = "Port should be < "+Short.MAX_VALUE
|
||||||
|
}
|
||||||
var ccp = ccpInput.selectedCountryNameCode
|
var ccp = ccpInput.selectedCountryNameCode
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
var pi = PeerInfo(schema, InetAddress.getByName(ip), port, ccp)
|
var pi = PeerInfo(schema, InetAddress.getByName(ip), port, ccp)
|
||||||
try {
|
try {
|
||||||
var ping = ping(pi.address, pi.port)
|
var ping = ping(pi.hostName, pi.port)
|
||||||
pi.ping = ping
|
pi.ping = ping
|
||||||
} catch(e: Throwable){
|
} catch (e: Throwable){
|
||||||
pi.ping = Int.MAX_VALUE
|
pi.ping = Int.MAX_VALUE
|
||||||
}
|
}
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
@ -216,34 +334,60 @@ class PeerListActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
// Inflate the menu; this adds items to the action bar if it is present.
|
// Inflate the menu; this adds items to the action bar if it is present.
|
||||||
menuInflater.inflate(R.menu.save, menu)
|
menuInflater.inflate(R.menu.save_peers, menu)
|
||||||
val item = menu.findItem(R.id.saveItem) as MenuItem
|
val item = menu.findItem(R.id.saveItem) as MenuItem
|
||||||
item.setActionView(R.layout.menu_save)
|
item.setActionView(R.layout.menu_save)
|
||||||
val saveButton = item
|
val saveButton = item
|
||||||
.actionView.findViewById<Button>(R.id.saveButton)
|
.actionView.findViewById<Button>(R.id.saveButton)
|
||||||
saveButton.setOnClickListener {
|
saveButton.setOnClickListener {
|
||||||
if(isLoading){
|
saveButton.isClickable = false
|
||||||
return@setOnClickListener
|
cancelPeerListPing()
|
||||||
}
|
|
||||||
val result = Intent(this, MainActivity::class.java)
|
val result = Intent(this, MainActivity::class.java)
|
||||||
var adapter = findViewById<ListView>(R.id.peerList).adapter as SelectPeerInfoListAdapter
|
var adapter = findViewById<ListView>(R.id.peerList).adapter as SelectPeerInfoListAdapter
|
||||||
val selectedPeers = adapter.getSelectedPeers()
|
val selectedPeers = adapter.getSelectedPeers()
|
||||||
/* WiFi Direct test - no peers is needed
|
|
||||||
if(selectedPeers.size>0) {
|
|
||||||
result.putExtra(MainActivity.PEER_LIST, serializePeerInfoSet2StringList(selectedPeers))
|
|
||||||
setResult(Activity.RESULT_OK, result)
|
|
||||||
finish()
|
|
||||||
} else {
|
|
||||||
val text = "Select at least one peer"
|
|
||||||
val duration = Toast.LENGTH_SHORT
|
|
||||||
val toast = Toast.makeText(applicationContext, text, duration)
|
|
||||||
toast.setGravity(Gravity.CENTER, 0, 0)
|
|
||||||
toast.show()
|
|
||||||
}*/
|
|
||||||
result.putExtra(MainActivity.PEER_LIST, serializePeerInfoSet2StringList(selectedPeers))
|
result.putExtra(MainActivity.PEER_LIST, serializePeerInfoSet2StringList(selectedPeers))
|
||||||
setResult(Activity.RESULT_OK, result)
|
setResult(Activity.RESULT_OK, result)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val editUrl = menu.findItem(R.id.editUrlItem) as MenuItem
|
||||||
|
editUrl.setActionView(R.layout.menu_edit_url)
|
||||||
|
val editUrlButton = editUrl
|
||||||
|
.actionView.findViewById<Button>(R.id.editUrlButton)
|
||||||
|
editUrlButton.setOnClickListener {
|
||||||
|
editPeerListUrl()
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun cancelPeerListPing() {
|
||||||
|
peerListPing = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
super.onStop()
|
||||||
|
cancelPeerListPing()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SizeOfPeerList: SizeOf<List<PeerInfo>> {
|
||||||
|
|
||||||
|
override fun sizeOf(obj: List<PeerInfo>): Int{
|
||||||
|
var size = 0
|
||||||
|
for (o in obj) {
|
||||||
|
if (o.hostName != null) {
|
||||||
|
size += o.hostName.length * 2
|
||||||
|
}
|
||||||
|
if (o.schema != null) {
|
||||||
|
size += o.schema.length * 2
|
||||||
|
}
|
||||||
|
if (o.countryCode != null) {
|
||||||
|
size += o.countryCode!!.length * 2
|
||||||
|
}
|
||||||
|
size += 4
|
||||||
|
size += 4
|
||||||
|
size += 1
|
||||||
|
}
|
||||||
|
return size
|
||||||
|
}
|
||||||
}
|
}
|
@ -27,39 +27,32 @@ import mobile.Mobile
|
|||||||
import mobile.Yggdrasil
|
import mobile.Yggdrasil
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.net.Inet6Address
|
import java.net.Inet6Address
|
||||||
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
class YggdrasilTunService : VpnService() {
|
class YggdrasilTunService : VpnService() {
|
||||||
|
|
||||||
private lateinit var ygg: Yggdrasil
|
private lateinit var ygg: Yggdrasil
|
||||||
|
private lateinit var tunInputStream: InputStream
|
||||||
|
private lateinit var tunOutputStream: OutputStream
|
||||||
|
private lateinit var address: String
|
||||||
private var isClosed = false
|
private var isClosed = false
|
||||||
|
|
||||||
/** Maximum packet size is constrained by the MTU, which is given as a signed short/2 */
|
/** Maximum packet size is constrained by the MTU, which is given as a signed short/2 */
|
||||||
private val MAX_PACKET_SIZE = Short.MAX_VALUE/2
|
private val MAX_PACKET_SIZE = Short.MAX_VALUE/2
|
||||||
|
private var tunInterface: ParcelFileDescriptor? = null
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "Yggdrasil-service"
|
private const val TAG = "Yggdrasil-service"
|
||||||
}
|
}
|
||||||
private var tunInterface: ParcelFileDescriptor? = null
|
|
||||||
private var tunInputStream: InputStream? = null
|
|
||||||
private var tunOutputStream: OutputStream? = null
|
|
||||||
private var scope: CoroutineScope? = null
|
|
||||||
private var address: String? = null
|
|
||||||
|
|
||||||
private var mNotificationManager: NotificationManager? = null
|
|
||||||
|
|
||||||
private val FOREGROUND_ID = 1338
|
private val FOREGROUND_ID = 1338
|
||||||
|
|
||||||
override fun onCreate() {
|
|
||||||
super.onCreate()
|
|
||||||
mNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
val pi: PendingIntent? = intent?.getParcelableExtra(MainActivity.PARAM_PINTENT)
|
val pi: PendingIntent? = intent?.getParcelableExtra(MainActivity.PARAM_PINTENT)
|
||||||
when(intent?.getStringExtra(MainActivity.COMMAND)){
|
when(intent?.getStringExtra(MainActivity.COMMAND)){
|
||||||
MainActivity.STOP ->{
|
MainActivity.STOP ->{
|
||||||
stopVpn(pi)
|
stopVpn(pi)
|
||||||
startForeground(FOREGROUND_ID, foregroundNotification("Yggdrasil service stopped"))
|
foregroundNotification(FOREGROUND_ID, "Yggdrasil service stopped")
|
||||||
}
|
}
|
||||||
MainActivity.START ->{
|
MainActivity.START ->{
|
||||||
val peers = deserializeStringList2PeerInfoSet(intent.getStringArrayListExtra(MainActivity.CURRENT_PEERS))
|
val peers = deserializeStringList2PeerInfoSet(intent.getStringArrayListExtra(MainActivity.CURRENT_PEERS))
|
||||||
@ -67,7 +60,7 @@ class YggdrasilTunService : VpnService() {
|
|||||||
val staticIP: Boolean = intent.getBooleanExtra(MainActivity.STATIC_IP, false)
|
val staticIP: Boolean = intent.getBooleanExtra(MainActivity.STATIC_IP, false)
|
||||||
ygg = Yggdrasil()
|
ygg = Yggdrasil()
|
||||||
setupTunInterface(pi, peers, dns, staticIP)
|
setupTunInterface(pi, peers, dns, staticIP)
|
||||||
startForeground(FOREGROUND_ID, foregroundNotification("Yggdrasil service started"))
|
foregroundNotification(FOREGROUND_ID, "Yggdrasil service started")
|
||||||
}
|
}
|
||||||
MainActivity.UPDATE_DNS ->{
|
MainActivity.UPDATE_DNS ->{
|
||||||
val dns = deserializeStringList2DNSInfoSet(intent.getStringArrayListExtra(MainActivity.CURRENT_DNS))
|
val dns = deserializeStringList2DNSInfoSet(intent.getStringArrayListExtra(MainActivity.CURRENT_DNS))
|
||||||
@ -84,11 +77,18 @@ class YggdrasilTunService : VpnService() {
|
|||||||
private fun setupIOStreams(dns: MutableSet<DNSInfo>){
|
private fun setupIOStreams(dns: MutableSet<DNSInfo>){
|
||||||
address = ygg.addressString
|
address = ygg.addressString
|
||||||
|
|
||||||
var builder = Builder()
|
var builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
.addAddress(address!!, 7)
|
Builder()
|
||||||
|
.addAddress(address, 7)
|
||||||
.allowFamily(OsConstants.AF_INET)
|
.allowFamily(OsConstants.AF_INET)
|
||||||
.allowBypass()
|
.allowBypass()
|
||||||
|
.setBlocking(true)
|
||||||
.setMtu(MAX_PACKET_SIZE)
|
.setMtu(MAX_PACKET_SIZE)
|
||||||
|
} else {
|
||||||
|
Builder()
|
||||||
|
.addAddress(address, 7)
|
||||||
|
.setMtu(MAX_PACKET_SIZE)
|
||||||
|
}
|
||||||
if (dns.size > 0) {
|
if (dns.size > 0) {
|
||||||
for (d in dns) {
|
for (d in dns) {
|
||||||
builder.addDnsServer(d.address)
|
builder.addDnsServer(d.address)
|
||||||
@ -100,15 +100,9 @@ class YggdrasilTunService : VpnService() {
|
|||||||
if(!hasIpv6DefaultRoute()){
|
if(!hasIpv6DefaultRoute()){
|
||||||
builder.addRoute("2000::",3)
|
builder.addRoute("2000::",3)
|
||||||
}
|
}
|
||||||
if(tunInterface!=null){
|
|
||||||
tunInterface!!.close()
|
|
||||||
tunInputStream!!.close()
|
|
||||||
tunOutputStream!!.close()
|
|
||||||
}
|
|
||||||
tunInterface = builder.establish()
|
tunInterface = builder.establish()
|
||||||
tunInputStream = FileInputStream(tunInterface!!.fileDescriptor)
|
tunInputStream = FileInputStream(tunInterface!!.fileDescriptor)
|
||||||
tunOutputStream = FileOutputStream(tunInterface!!.fileDescriptor)
|
tunOutputStream = FileOutputStream(tunInterface!!.fileDescriptor)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupTunInterface(
|
private fun setupTunInterface(
|
||||||
@ -129,15 +123,13 @@ class YggdrasilTunService : VpnService() {
|
|||||||
|
|
||||||
setupIOStreams(dns)
|
setupIOStreams(dns)
|
||||||
|
|
||||||
val job = SupervisorJob()
|
thread(start = true) {
|
||||||
scope = CoroutineScope(Dispatchers.Default + job)
|
val buffer = ByteArray(MAX_PACKET_SIZE)
|
||||||
scope!!.launch {
|
|
||||||
val buffer = ByteArray(2048)
|
|
||||||
while (!isClosed) {
|
while (!isClosed) {
|
||||||
readPacketsFromTun(yggConduitEndpoint, buffer)
|
readPacketsFromTun(yggConduitEndpoint, buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scope!!.launch {
|
thread(start = true) {
|
||||||
while (!isClosed) {
|
while (!isClosed) {
|
||||||
writePacketsToTun(yggConduitEndpoint)
|
writePacketsToTun(yggConduitEndpoint)
|
||||||
}
|
}
|
||||||
@ -148,7 +140,7 @@ class YggdrasilTunService : VpnService() {
|
|||||||
|
|
||||||
private fun sendMeshPeerStatus(pi: PendingIntent?){
|
private fun sendMeshPeerStatus(pi: PendingIntent?){
|
||||||
class Token : TypeToken<List<Peer>>()
|
class Token : TypeToken<List<Peer>>()
|
||||||
var add = ygg.addressString
|
ygg.addressString
|
||||||
var meshPeers: List<Peer> = gson.fromJson(ygg.peersJSON, Token().type)
|
var meshPeers: List<Peer> = gson.fromJson(ygg.peersJSON, Token().type)
|
||||||
val intent: Intent = Intent().putStringArrayListExtra(
|
val intent: Intent = Intent().putStringArrayListExtra(
|
||||||
MainActivity.MESH_PEERS,
|
MainActivity.MESH_PEERS,
|
||||||
@ -185,7 +177,7 @@ class YggdrasilTunService : VpnService() {
|
|||||||
.putString(MainActivity.signingPublicKey, signingPublicKey)
|
.putString(MainActivity.signingPublicKey, signingPublicKey)
|
||||||
.putString(MainActivity.encryptionPrivateKey, encryptionPrivateKey)
|
.putString(MainActivity.encryptionPrivateKey, encryptionPrivateKey)
|
||||||
.putString(MainActivity.encryptionPublicKey, encryptionPublicKey)
|
.putString(MainActivity.encryptionPublicKey, encryptionPublicKey)
|
||||||
.putString(MainActivity.STATIC_IP,MainActivity.STATIC_IP).apply()
|
.putString(MainActivity.STATIC_IP, MainActivity.STATIC_IP).apply()
|
||||||
} else {
|
} else {
|
||||||
val signingPrivateKey = preferences.getString(MainActivity.signingPrivateKey, null)
|
val signingPrivateKey = preferences.getString(MainActivity.signingPrivateKey, null)
|
||||||
val signingPublicKey = preferences.getString(MainActivity.signingPublicKey, null)
|
val signingPublicKey = preferences.getString(MainActivity.signingPublicKey, null)
|
||||||
@ -219,12 +211,8 @@ class YggdrasilTunService : VpnService() {
|
|||||||
private fun readPacketsFromTun(yggConduitEndpoint: ConduitEndpoint, buffer: ByteArray) {
|
private fun readPacketsFromTun(yggConduitEndpoint: ConduitEndpoint, buffer: ByteArray) {
|
||||||
try {
|
try {
|
||||||
// Read the outgoing packet from the input stream.
|
// Read the outgoing packet from the input stream.
|
||||||
val length = tunInputStream?.read(buffer) ?: 1
|
val length = tunInputStream.read(buffer)
|
||||||
if (length > 0){
|
|
||||||
yggConduitEndpoint.send(buffer.sliceArray(IntRange(0, length - 1)))
|
yggConduitEndpoint.send(buffer.sliceArray(IntRange(0, length - 1)))
|
||||||
} else {
|
|
||||||
Thread.sleep(100)
|
|
||||||
}
|
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
@ -234,7 +222,7 @@ class YggdrasilTunService : VpnService() {
|
|||||||
val buffer = yggConduitEndpoint.recv()
|
val buffer = yggConduitEndpoint.recv()
|
||||||
if(buffer!=null) {
|
if(buffer!=null) {
|
||||||
try {
|
try {
|
||||||
tunOutputStream?.write(buffer)
|
tunOutputStream.write(buffer)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
@ -243,11 +231,9 @@ class YggdrasilTunService : VpnService() {
|
|||||||
|
|
||||||
private fun stopVpn(pi: PendingIntent?) {
|
private fun stopVpn(pi: PendingIntent?) {
|
||||||
isClosed = true;
|
isClosed = true;
|
||||||
scope!!.coroutineContext.cancelChildren()
|
tunInputStream.close()
|
||||||
tunInputStream!!.close()
|
tunOutputStream.close()
|
||||||
tunOutputStream!!.close()
|
|
||||||
tunInterface!!.close()
|
tunInterface!!.close()
|
||||||
tunInterface = null
|
|
||||||
Log.d(TAG,"Stop is running from service")
|
Log.d(TAG,"Stop is running from service")
|
||||||
ygg.stop()
|
ygg.stop()
|
||||||
val intent: Intent = Intent()
|
val intent: Intent = Intent()
|
||||||
@ -265,7 +251,9 @@ class YggdrasilTunService : VpnService() {
|
|||||||
private fun hasIpv6DefaultRoute(): Boolean {
|
private fun hasIpv6DefaultRoute(): Boolean {
|
||||||
val cm =
|
val cm =
|
||||||
getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
val networks = cm.allNetworks
|
val networks = cm.allNetworks
|
||||||
|
|
||||||
for (network in networks) {
|
for (network in networks) {
|
||||||
val linkProperties = cm.getLinkProperties(network)
|
val linkProperties = cm.getLinkProperties(network)
|
||||||
if(linkProperties!=null) {
|
if(linkProperties!=null) {
|
||||||
@ -277,10 +265,12 @@ class YggdrasilTunService : VpnService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun foregroundNotification(text: String): Notification? {
|
private fun foregroundNotification(FOREGROUND_ID: Int, text: String) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||||
val channelId =
|
val channelId =
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
createNotificationChannel(TAG, "Yggdrasil service")
|
createNotificationChannel(TAG, "Yggdrasil service")
|
||||||
@ -300,7 +290,8 @@ class YggdrasilTunService : VpnService() {
|
|||||||
.setContentText(text)
|
.setContentText(text)
|
||||||
.setSmallIcon(R.mipmap.ic_launcher)
|
.setSmallIcon(R.mipmap.ic_launcher)
|
||||||
.setTicker(text)
|
.setTicker(text)
|
||||||
return b.build()
|
startForeground(FOREGROUND_ID, b.build())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
package io.github.chronosx88.yggdrasil.models
|
||||||
|
|
||||||
|
class NodeInfo {
|
||||||
|
|
||||||
|
constructor(key: String, value: String){
|
||||||
|
this.key = key
|
||||||
|
this.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var key: String
|
||||||
|
var value: String
|
||||||
|
|
||||||
|
}
|
@ -7,9 +7,13 @@ import java.net.InetAddress
|
|||||||
|
|
||||||
|
|
||||||
class PeerInfo {
|
class PeerInfo {
|
||||||
|
|
||||||
|
constructor(){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
constructor(schema: String, address: InetAddress, port: Int, countryCode: String){
|
constructor(schema: String, address: InetAddress, port: Int, countryCode: String){
|
||||||
this.schema = schema
|
this.schema = schema
|
||||||
this.address = address
|
|
||||||
var a = address.toString();
|
var a = address.toString();
|
||||||
if(a.lastIndexOf('/')>0){
|
if(a.lastIndexOf('/')>0){
|
||||||
this.hostName = a.split("/")[0]
|
this.hostName = a.split("/")[0]
|
||||||
@ -22,7 +26,6 @@ class PeerInfo {
|
|||||||
|
|
||||||
constructor(schema: String, address: InetAddress, port: Int, countryCode: String?, isMeshPeer: Boolean){
|
constructor(schema: String, address: InetAddress, port: Int, countryCode: String?, isMeshPeer: Boolean){
|
||||||
this.schema = schema
|
this.schema = schema
|
||||||
this.address = address
|
|
||||||
var a = address.toString();
|
var a = address.toString();
|
||||||
if(a.lastIndexOf('/')>0){
|
if(a.lastIndexOf('/')>0){
|
||||||
this.hostName = a.split("/")[0]
|
this.hostName = a.split("/")[0]
|
||||||
@ -34,9 +37,8 @@ class PeerInfo {
|
|||||||
this.isMeshPeer = isMeshPeer
|
this.isMeshPeer = isMeshPeer
|
||||||
}
|
}
|
||||||
|
|
||||||
var schema: String
|
lateinit var schema: String
|
||||||
var address: InetAddress
|
lateinit var hostName: String
|
||||||
var hostName: String
|
|
||||||
var port = 0
|
var port = 0
|
||||||
var countryCode: String?=null
|
var countryCode: String?=null
|
||||||
var ping: Int = Int.MAX_VALUE
|
var ping: Int = Int.MAX_VALUE
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
package io.github.chronosx88.yggdrasil.models.config
|
||||||
|
|
||||||
|
import android.content.ClipData
|
||||||
|
import android.content.ClipboardManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.Gravity
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.*
|
||||||
|
import io.github.chronosx88.yggdrasil.R
|
||||||
|
import io.github.chronosx88.yggdrasil.models.NodeInfo
|
||||||
|
|
||||||
|
class CopyInfoAdapter(
|
||||||
|
context: Context,
|
||||||
|
nodeInfoList: List<NodeInfo>,
|
||||||
|
) : ArrayAdapter<NodeInfo?> (context, 0, nodeInfoList) {
|
||||||
|
|
||||||
|
private val mContext: Context = context
|
||||||
|
private var nodeInfoList: MutableList<NodeInfo> = nodeInfoList as MutableList<NodeInfo>
|
||||||
|
|
||||||
|
override fun getItem(position: Int): NodeInfo? {
|
||||||
|
return nodeInfoList[position]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCount(): Int {
|
||||||
|
return nodeInfoList.size
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||||
|
var copyNodeInfoHolder = CopyInfoHolder()
|
||||||
|
var listItem: View? = convertView
|
||||||
|
if (listItem == null) {
|
||||||
|
listItem = LayoutInflater.from(mContext).inflate(R.layout.copy_node_info_list_item, parent, false)
|
||||||
|
copyNodeInfoHolder.copyButton = listItem.findViewById(R.id.nodeInfoButton) as Button
|
||||||
|
copyNodeInfoHolder.nodeInfoText = listItem.findViewById(R.id.nodeInfoText) as TextView
|
||||||
|
copyNodeInfoHolder.nodeInfoKey = listItem.findViewById(R.id.nodeInfoKey) as TextView
|
||||||
|
listItem.tag = copyNodeInfoHolder
|
||||||
|
} else {
|
||||||
|
copyNodeInfoHolder = listItem.tag as CopyInfoHolder
|
||||||
|
}
|
||||||
|
copyNodeInfoHolder.nodeInfoKey.text = nodeInfoList[position].key
|
||||||
|
copyNodeInfoHolder.nodeInfoText.text = nodeInfoList[position].value
|
||||||
|
copyNodeInfoHolder.copyButton.setOnClickListener{ _ ->
|
||||||
|
val clipboard: ClipboardManager =
|
||||||
|
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
|
val clip =
|
||||||
|
ClipData.newPlainText(nodeInfoList[position].key, nodeInfoList[position].value)
|
||||||
|
clipboard.setPrimaryClip(clip)
|
||||||
|
showToast(nodeInfoList[position].key + " " + context.getString(R.string.node_info_copied))
|
||||||
|
}
|
||||||
|
return listItem!!
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showToast(text: String){
|
||||||
|
val duration = Toast.LENGTH_SHORT
|
||||||
|
val toast = Toast.makeText(context, text, duration)
|
||||||
|
toast.setGravity(Gravity.CENTER, 0, 0)
|
||||||
|
toast.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
class CopyInfoHolder {
|
||||||
|
lateinit var nodeInfoKey: TextView
|
||||||
|
lateinit var nodeInfoText: TextView
|
||||||
|
lateinit var copyButton: Button
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package io.github.chronosx88.yggdrasil.models.config
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkUtils {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
val TYPE_WIFI = 1
|
||||||
|
val TYPE_MOBILE = 2
|
||||||
|
val TYPE_NOT_CONNECTED = 0
|
||||||
|
val NETWORK_STATUS_NOT_CONNECTED = 0
|
||||||
|
val NETWORK_STATUS_WIFI = 1
|
||||||
|
val NETWORK_STATUS_MOBILE = 2
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getConnectivityStatus(context: Context): Int {
|
||||||
|
val cm =
|
||||||
|
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
|
val activeNetwork = cm.activeNetworkInfo
|
||||||
|
if (null != activeNetwork) {
|
||||||
|
if (activeNetwork.type == ConnectivityManager.TYPE_WIFI) return TYPE_WIFI
|
||||||
|
if (activeNetwork.type == ConnectivityManager.TYPE_MOBILE) return TYPE_MOBILE
|
||||||
|
}
|
||||||
|
return TYPE_NOT_CONNECTED
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getConnectivityStatusString(context: Context): Int {
|
||||||
|
val conn: Int = getConnectivityStatus(context)
|
||||||
|
var status = 0
|
||||||
|
if (conn == TYPE_WIFI) {
|
||||||
|
status = NETWORK_STATUS_WIFI
|
||||||
|
} else if (conn == TYPE_MOBILE) {
|
||||||
|
status = NETWORK_STATUS_MOBILE
|
||||||
|
} else if (conn == TYPE_NOT_CONNECTED) {
|
||||||
|
status = NETWORK_STATUS_NOT_CONNECTED
|
||||||
|
}
|
||||||
|
return status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,14 @@
|
|||||||
package io.github.chronosx88.yggdrasil.models.config
|
package io.github.chronosx88.yggdrasil.models.config
|
||||||
|
|
||||||
|
import android.content.ClipData
|
||||||
|
import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.view.Gravity
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.*
|
||||||
import android.widget.CheckBox
|
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.TextView
|
|
||||||
import io.github.chronosx88.yggdrasil.R
|
import io.github.chronosx88.yggdrasil.R
|
||||||
import io.github.chronosx88.yggdrasil.models.DNSInfo
|
import io.github.chronosx88.yggdrasil.models.DNSInfo
|
||||||
|
|
||||||
@ -66,6 +66,14 @@ class SelectDNSInfoListAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
dnsInfoHolder.dnsInfoText.setOnClickListener {
|
||||||
|
val clipboard: ClipboardManager =
|
||||||
|
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
|
val clip =
|
||||||
|
ClipData.newPlainText("DNS info", dnsId)
|
||||||
|
clipboard.setPrimaryClip(clip)
|
||||||
|
showToast(dnsId + " " + context.getString(R.string.node_info_copied))
|
||||||
|
}
|
||||||
dnsInfoHolder.checkbox.isChecked = this.currentDNS.contains(currentDNS)
|
dnsInfoHolder.checkbox.isChecked = this.currentDNS.contains(currentDNS)
|
||||||
return listItem!!
|
return listItem!!
|
||||||
}
|
}
|
||||||
@ -100,4 +108,11 @@ class SelectDNSInfoListAdapter(
|
|||||||
lateinit var dnsInfoText: TextView
|
lateinit var dnsInfoText: TextView
|
||||||
lateinit var ping: TextView
|
lateinit var ping: TextView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showToast(text: String){
|
||||||
|
val duration = Toast.LENGTH_SHORT
|
||||||
|
val toast = Toast.makeText(context, text, duration)
|
||||||
|
toast.setGravity(Gravity.CENTER, 0, 0)
|
||||||
|
toast.show()
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,14 +1,14 @@
|
|||||||
package io.github.chronosx88.yggdrasil.models.config
|
package io.github.chronosx88.yggdrasil.models.config
|
||||||
|
|
||||||
|
import android.content.ClipData
|
||||||
|
import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.view.Gravity
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.*
|
||||||
import android.widget.CheckBox
|
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.TextView
|
|
||||||
import io.github.chronosx88.yggdrasil.R
|
import io.github.chronosx88.yggdrasil.R
|
||||||
import io.github.chronosx88.yggdrasil.models.PeerInfo
|
import io.github.chronosx88.yggdrasil.models.PeerInfo
|
||||||
|
|
||||||
@ -18,7 +18,6 @@ class SelectPeerInfoListAdapter(
|
|||||||
currentPeers: MutableSet<PeerInfo>
|
currentPeers: MutableSet<PeerInfo>
|
||||||
) : ArrayAdapter<PeerInfo?> (context, 0, allPeers) {
|
) : ArrayAdapter<PeerInfo?> (context, 0, allPeers) {
|
||||||
|
|
||||||
private var isLoading = true
|
|
||||||
private val mContext: Context = context
|
private val mContext: Context = context
|
||||||
private var allPeers: MutableList<PeerInfo> = allPeers as MutableList<PeerInfo>
|
private var allPeers: MutableList<PeerInfo> = allPeers as MutableList<PeerInfo>
|
||||||
private var currentPeers: MutableSet<PeerInfo> = currentPeers
|
private var currentPeers: MutableSet<PeerInfo> = currentPeers
|
||||||
@ -47,17 +46,15 @@ class SelectPeerInfoListAdapter(
|
|||||||
val currentPeer = allPeers[position]
|
val currentPeer = allPeers[position]
|
||||||
peerInfoHolder.countryFlag.setImageResource(currentPeer.getCountry(mContext)!!.flagID)
|
peerInfoHolder.countryFlag.setImageResource(currentPeer.getCountry(mContext)!!.flagID)
|
||||||
val peerId = currentPeer.toString()
|
val peerId = currentPeer.toString()
|
||||||
if(currentPeer.ping == Int.MAX_VALUE){
|
|
||||||
peerInfoHolder.peerInfoText.text = peerId
|
peerInfoHolder.peerInfoText.text = peerId
|
||||||
|
if(currentPeer.ping == Int.MAX_VALUE){
|
||||||
peerInfoHolder.ping.text=""
|
peerInfoHolder.ping.text=""
|
||||||
peerInfoHolder.peerInfoText.setTextColor(Color.GRAY)
|
peerInfoHolder.peerInfoText.setTextColor(Color.GRAY)
|
||||||
} else {
|
} else {
|
||||||
peerInfoHolder.peerInfoText.text = peerId
|
|
||||||
peerInfoHolder.ping.text = currentPeer.ping.toString() + " ms"
|
peerInfoHolder.ping.text = currentPeer.ping.toString() + " ms"
|
||||||
peerInfoHolder.peerInfoText.setTextColor(Color.WHITE)
|
peerInfoHolder.peerInfoText.setTextColor(Color.WHITE)
|
||||||
}
|
}
|
||||||
peerInfoHolder.checkbox.setOnCheckedChangeListener { _, isChecked ->
|
peerInfoHolder.checkbox.setOnCheckedChangeListener { _, isChecked ->
|
||||||
if(!isLoading) {
|
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
if (!currentPeers.contains(currentPeer)) {
|
if (!currentPeers.contains(currentPeer)) {
|
||||||
currentPeers.add(currentPeer)
|
currentPeers.add(currentPeer)
|
||||||
@ -68,6 +65,13 @@ class SelectPeerInfoListAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
peerInfoHolder.peerInfoText.setOnClickListener {
|
||||||
|
val clipboard: ClipboardManager =
|
||||||
|
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
|
val clip =
|
||||||
|
ClipData.newPlainText("Peer info", peerId)
|
||||||
|
clipboard.setPrimaryClip(clip)
|
||||||
|
showToast(peerId + " " + context.getString(R.string.node_info_copied))
|
||||||
}
|
}
|
||||||
peerInfoHolder.checkbox.isChecked = this.currentPeers.contains(currentPeer)
|
peerInfoHolder.checkbox.isChecked = this.currentPeers.contains(currentPeer)
|
||||||
return listItem!!
|
return listItem!!
|
||||||
@ -77,9 +81,15 @@ class SelectPeerInfoListAdapter(
|
|||||||
return currentPeers
|
return currentPeers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getAllPeers(): List<PeerInfo> {
|
||||||
|
return allPeers
|
||||||
|
}
|
||||||
|
|
||||||
fun addItem(peerInfo: PeerInfo){
|
fun addItem(peerInfo: PeerInfo){
|
||||||
|
if(!allPeers.contains(peerInfo)){
|
||||||
allPeers.add(peerInfo)
|
allPeers.add(peerInfo)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun addItem(index: Int, peerInfo: PeerInfo){
|
fun addItem(index: Int, peerInfo: PeerInfo){
|
||||||
allPeers.add(index, peerInfo)
|
allPeers.add(index, peerInfo)
|
||||||
@ -97,10 +107,6 @@ class SelectPeerInfoListAdapter(
|
|||||||
this.notifyDataSetChanged()
|
this.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setLoading(loading: Boolean){
|
|
||||||
this.isLoading = loading
|
|
||||||
}
|
|
||||||
|
|
||||||
class PeerInfoHolder {
|
class PeerInfoHolder {
|
||||||
lateinit var checkbox: CheckBox
|
lateinit var checkbox: CheckBox
|
||||||
lateinit var countryFlag: ImageView
|
lateinit var countryFlag: ImageView
|
||||||
@ -108,4 +114,10 @@ class SelectPeerInfoListAdapter(
|
|||||||
lateinit var ping: TextView
|
lateinit var ping: TextView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showToast(text: String){
|
||||||
|
val duration = Toast.LENGTH_SHORT
|
||||||
|
val toast = Toast.makeText(context, text, duration)
|
||||||
|
toast.setGravity(Gravity.CENTER, 0, 0)
|
||||||
|
toast.show()
|
||||||
|
}
|
||||||
}
|
}
|
@ -77,15 +77,15 @@ class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun ping(address: InetAddress, port:Int): Int {
|
fun ping(hostname: String, port:Int): Int {
|
||||||
val start = System.currentTimeMillis()
|
val start = System.currentTimeMillis()
|
||||||
val socket = Socket()
|
val socket = Socket()
|
||||||
try {
|
try {
|
||||||
socket.connect(InetSocketAddress(address, port), 5000)
|
socket.connect(InetSocketAddress(hostname, port), 5000)
|
||||||
socket.close()
|
socket.close()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
print(address)
|
print(hostname)
|
||||||
return Int.MAX_VALUE
|
return Int.MAX_VALUE
|
||||||
}
|
}
|
||||||
return (System.currentTimeMillis() - start).toInt()
|
return (System.currentTimeMillis() - start).toInt()
|
||||||
|
26
app/src/main/res/layout/activity_about.xml
Normal file
26
app/src/main/res/layout/activity_about.xml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout 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=".AboutActivity"
|
||||||
|
android:background="@color/grey">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:theme="@style/AppTheme.AppBarOverlay">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:background="?attr/colorPrimary"
|
||||||
|
app:popupTheme="@style/AppTheme.PopupOverlay" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<include layout="@layout/content_about" />
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
26
app/src/main/res/layout/activity_copy_local_node_info.xml
Normal file
26
app/src/main/res/layout/activity_copy_local_node_info.xml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout 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=".CopyLocalNodeInfoActivity"
|
||||||
|
android:background="@color/grey">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:theme="@style/AppTheme.AppBarOverlay">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:background="?attr/colorPrimary"
|
||||||
|
app:popupTheme="@style/AppTheme.PopupOverlay" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<include layout="@layout/content_node_info" />
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
@ -27,9 +27,10 @@
|
|||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:popupTheme="@style/AppTheme.PopupOverlay" />
|
app:popupTheme="@style/AppTheme.PopupOverlay"
|
||||||
|
android:layout_marginRight="240dp" />
|
||||||
|
|
||||||
<Switch
|
<androidx.appcompat.widget.SwitchCompat
|
||||||
android:id="@+id/staticIP"
|
android:id="@+id/staticIP"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -41,7 +42,7 @@
|
|||||||
android:text="Static IP"
|
android:text="Static IP"
|
||||||
android:textColor="@color/white"/>
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
<Switch
|
<androidx.appcompat.widget.SwitchCompat
|
||||||
android:id="@+id/switchOn"
|
android:id="@+id/switchOn"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
22
app/src/main/res/layout/content_about.xml
Normal file
22
app/src/main/res/layout/content_about.xml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:background="@color/grey"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/about"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="20dp">
|
||||||
|
</TextView>
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
@ -35,10 +35,10 @@
|
|||||||
android:textColor="@color/dark_30"
|
android:textColor="@color/dark_30"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/copyIp"
|
android:id="@+id/nodeInfo"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:text="COPY"
|
android:text="INFO"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
android:background="@android:color/transparent"/>
|
android:background="@android:color/transparent"/>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@ -53,7 +53,7 @@
|
|||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/copyIp"
|
app:layout_constraintEnd_toStartOf="@+id/nodeInfo"
|
||||||
android:text=""
|
android:text=""
|
||||||
android:textColor="@color/white"/>
|
android:textColor="@color/white"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@ -152,5 +152,12 @@
|
|||||||
app:layout_constraintTop_toTopOf="parent"/>
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
<Button
|
||||||
|
android:id="@+id/about"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
android:text=""
|
||||||
|
android:textSize="10sp" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
13
app/src/main/res/layout/content_node_info.xml
Normal file
13
app/src/main/res/layout/content_node_info.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?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:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
<ListView
|
||||||
|
android:id="@+id/nodeInfoList"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:divider="@color/dark_20"
|
||||||
|
android:dividerHeight="2px"/>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
52
app/src/main/res/layout/copy_node_info_list_item.xml
Normal file
52
app/src/main/res/layout/copy_node_info_list_item.xml
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<RelativeLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_toLeftOf="@+id/nodeInfoButton"
|
||||||
|
android:id="@+id/data">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/nodeInfoKey"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:textSize="10sp"
|
||||||
|
android:text = "nodeInfoKey"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/nodeInfoText"
|
||||||
|
android:layout_marginLeft="10dp"
|
||||||
|
android:paddingBottom="5dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/nodeInfoText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:layout_marginLeft="10dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:minHeight="35dp"
|
||||||
|
android:text="nodeInfoText"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/nodeInfoKey"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/nodeInfoButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="COPY"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:background="@android:color/transparent"/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
43
app/src/main/res/layout/edit_peer_list_url_dialog.xml
Normal file
43
app/src/main/res/layout/edit_peer_list_url_dialog.xml
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:background="@color/grey">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/url"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
app:boxBackgroundMode="none"
|
||||||
|
android:background="@drawable/edit_text_rounded_corner"
|
||||||
|
android:textColorHint="@color/white"
|
||||||
|
style="@style/EditText.OutlinedBox">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/urlInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="URL"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textCursorDrawable="@null"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/save"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
|
android:layout_marginLeft="10dp"
|
||||||
|
android:layout_marginRight="10dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/url"
|
||||||
|
android:background="@drawable/button_selector"
|
||||||
|
app:backgroundTint="@null"
|
||||||
|
android:text="SAVE"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -22,5 +22,6 @@
|
|||||||
android:paddingEnd="20dp"
|
android:paddingEnd="20dp"
|
||||||
android:minHeight="22dp"
|
android:minHeight="22dp"
|
||||||
android:textSize="13sp"
|
android:textSize="13sp"
|
||||||
android:textColor="@color/white"/>
|
android:textColor="@color/white"
|
||||||
|
android:paddingRight="20dp" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
@ -11,7 +11,8 @@
|
|||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@+id/hostInfoText"
|
app:layout_constraintStart_toEndOf="@+id/hostInfoText"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"/>
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
android:layout_marginLeft="10dp" />
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/hostInfoText"
|
android:id="@+id/hostInfoText"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
@ -24,7 +25,8 @@
|
|||||||
app:layout_constraintEnd_toStartOf="@+id/ping"
|
app:layout_constraintEnd_toStartOf="@+id/ping"
|
||||||
app:layout_constraintStart_toEndOf="@+id/countryFlag"
|
app:layout_constraintStart_toEndOf="@+id/countryFlag"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"/>
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
android:layout_marginLeft="10dp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/ping"
|
android:id="@+id/ping"
|
||||||
|
13
app/src/main/res/layout/menu_edit_url.xml
Normal file
13
app/src/main/res/layout/menu_edit_url.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
<Button xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/editUrlButton"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:text="EDIT URL"
|
||||||
|
android:background="@android:color/transparent"/>
|
||||||
|
</RelativeLayout>
|
12
app/src/main/res/menu/save_peers.xml
Normal file
12
app/src/main/res/menu/save_peers.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item android:id="@+id/editUrlItem"
|
||||||
|
android:title=""
|
||||||
|
app:actionLayout="@layout/menu_edit_url"
|
||||||
|
app:showAsAction="always"/>
|
||||||
|
<item android:id="@+id/saveItem"
|
||||||
|
android:title=""
|
||||||
|
app:actionLayout="@layout/menu_save"
|
||||||
|
app:showAsAction="always"/>
|
||||||
|
</menu>
|
7
app/src/main/res/values-v21/styles.xml
Normal file
7
app/src/main/res/values-v21/styles.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<style name="SwitchTheme" parent="Theme.AppCompat.Light">
|
||||||
|
<item name="colorControlActivated">@color/green</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
@ -5,6 +5,8 @@
|
|||||||
<string name="switch_button_title">SwitchOn</string>
|
<string name="switch_button_title">SwitchOn</string>
|
||||||
<string name="title_activity_peer_list">Edit peers</string>
|
<string name="title_activity_peer_list">Edit peers</string>
|
||||||
<string name="title_activity_dns_list">Edit DNS</string>
|
<string name="title_activity_dns_list">Edit DNS</string>
|
||||||
<string name="address_copied">Address copied</string>
|
<string name="node_info_copied">copied</string>
|
||||||
<string name="schema">Schema</string>
|
<string name="schema">Schema</string>
|
||||||
|
<string name="title_activity_about">Yggdrasil</string>
|
||||||
|
<string name="title_activity_copy_local_node_info">Node info</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="SwitchTheme" parent="Theme.AppCompat.Light">
|
<style name="SwitchTheme" parent="Theme.AppCompat.Light">
|
||||||
<item name="android:colorControlActivated">@color/green</item>
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="EditText.OutlinedBox" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
<style name="EditText.OutlinedBox" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
// 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.3.72'
|
ext.kotlin_version = '1.4.10'
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
|
mavenCentral()
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:4.0.1'
|
classpath 'com.android.tools.build:gradle:4.1.1'
|
||||||
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
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
|
4
fastlane/metadata/android/en-US/changelogs/1.txt
Normal file
4
fastlane/metadata/android/en-US/changelogs/1.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
v1.0
|
||||||
|
1. Implemented VPN network interface for Yggdrasil tunneling
|
||||||
|
2. Optimized memory buffer
|
||||||
|
3. Added IPv6 info
|
13
fastlane/metadata/android/en-US/changelogs/2.txt
Normal file
13
fastlane/metadata/android/en-US/changelogs/2.txt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
v1.2
|
||||||
|
* New DNS and new Peer form
|
||||||
|
* fix adding Peers when json url unavailable
|
||||||
|
* DNS dynamic list selection fixes
|
||||||
|
* ping selected Peers and DNS fixes
|
||||||
|
* fixed when app crashed sometimes after ygg switch off
|
||||||
|
* fixed high CPU consumption after switch-off.
|
||||||
|
* prevent NPE fix
|
||||||
|
|
||||||
|
v1.1
|
||||||
|
1. Added dynamic peer list loading and edit functionality
|
||||||
|
2. Layout minor changes
|
||||||
|
3. Peers ping test and sorting by response time
|
3
fastlane/metadata/android/en-US/changelogs/3.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/3.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
v1.3
|
||||||
|
1. open MainActivity after Notification click, task (#12)
|
||||||
|
2. new DNS only number issue
|
3
fastlane/metadata/android/en-US/changelogs/4.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/4.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
v1.4
|
||||||
|
* F-Droid support
|
||||||
|
* Android 4.0.3 support (API 15)
|
4
fastlane/metadata/android/en-US/full_description.txt
Normal file
4
fastlane/metadata/android/en-US/full_description.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
The application for connecting to Yggdrasil network on Android
|
||||||
|
|
||||||
|
Once connected, you can access yggdrasil public services,
|
||||||
|
as the application has peered automatically to default peers within the application
|
BIN
fastlane/metadata/android/en-US/images/phoneScreenshots/001.png
Normal file
BIN
fastlane/metadata/android/en-US/images/phoneScreenshots/001.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
BIN
fastlane/metadata/android/en-US/images/phoneScreenshots/002.png
Normal file
BIN
fastlane/metadata/android/en-US/images/phoneScreenshots/002.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
BIN
fastlane/metadata/android/en-US/images/phoneScreenshots/003.png
Normal file
BIN
fastlane/metadata/android/en-US/images/phoneScreenshots/003.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
1
fastlane/metadata/android/en-US/short_description.txt
Normal file
1
fastlane/metadata/android/en-US/short_description.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
The application for connecting to Yggdrasil network on Android
|
1
fastlane/metadata/android/en-US/title.txt
Normal file
1
fastlane/metadata/android/en-US/title.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Yggdrasil
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
|||||||
#Tue Jun 09 01:07:00 PDT 2020
|
#Wed Oct 14 16:00:08 EEST 2020
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
|
||||||
|
@ -4,7 +4,7 @@ all:
|
|||||||
-go get -u github.com/yggdrasil-network/yggdrasil-go;
|
-go get -u github.com/yggdrasil-network/yggdrasil-go;
|
||||||
-cd $(GOPATH)/src/github.com/yggdrasil-network/yggdrasil-go; \
|
-cd $(GOPATH)/src/github.com/yggdrasil-network/yggdrasil-go; \
|
||||||
go get -v -d ./...; \
|
go get -v -d ./...; \
|
||||||
go get -u github.com/vikulin/yggdrasil-extras@268b006; \
|
go get -u github.com/yggdrasil-network/yggdrasil-extras@005d79c; \
|
||||||
ANDROID=true ./build;
|
ANDROID=true ./build;
|
||||||
mv -f $(GOPATH)/src/github.com/yggdrasil-network/yggdrasil-go/yggdrasil.aar yggdrasil.aar;
|
mv -f $(GOPATH)/src/github.com/yggdrasil-network/yggdrasil-go/yggdrasil.aar yggdrasil.aar;
|
||||||
mv -f $(GOPATH)/src/github.com/yggdrasil-network/yggdrasil-go/yggdrasil-sources.jar yggdrasil-sources.jar;
|
mv -f $(GOPATH)/src/github.com/yggdrasil-network/yggdrasil-go/yggdrasil-sources.jar yggdrasil-sources.jar;
|
||||||
|
Loading…
Reference in New Issue
Block a user