mirror of
https://github.com/yggdrasil-network/crispa-android.git
synced 2024-11-14 14:31:03 +00:00
1. added DNS form. iteration 1.
This commit is contained in:
parent
aec660bff0
commit
3db311c4d2
@ -18,7 +18,10 @@
|
|||||||
android:name=".PeerListActivity"
|
android:name=".PeerListActivity"
|
||||||
android:label="@string/title_activity_peer_list"
|
android:label="@string/title_activity_peer_list"
|
||||||
android:theme="@style/AppTheme.NoActionBar" />
|
android:theme="@style/AppTheme.NoActionBar" />
|
||||||
|
<activity
|
||||||
|
android:name=".DNSListActivity"
|
||||||
|
android:label="@string/title_activity_dns_list"
|
||||||
|
android:theme="@style/AppTheme.NoActionBar" />
|
||||||
<service
|
<service
|
||||||
android:name=".YggdrasilTunService"
|
android:name=".YggdrasilTunService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
|
@ -0,0 +1,109 @@
|
|||||||
|
package io.github.chronosx88.yggdrasil
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.Gravity
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.ListView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import io.github.chronosx88.yggdrasil.models.DNSInfo
|
||||||
|
import io.github.chronosx88.yggdrasil.models.config.SelectDNSInfoListAdapter
|
||||||
|
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.ping
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import java.net.*
|
||||||
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
|
|
||||||
|
class DNSListActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val allDNS = arrayListOf(
|
||||||
|
DNSInfo(
|
||||||
|
InetAddress.getByName("[301:2522::53]"),
|
||||||
|
"CZ",
|
||||||
|
"DNS implementation for Yggdrasil. https://github.com/Revertron/wyrd"),
|
||||||
|
DNSInfo(InetAddress.getByName("[301:2923::53]"),
|
||||||
|
"SK",
|
||||||
|
"DNS implementation for Yggdrasil. https://github.com/Revertron/wyrd"),
|
||||||
|
DNSInfo(InetAddress.getByName("[300:4523::53]"),
|
||||||
|
"DE",
|
||||||
|
"DNS implementation for Yggdrasil. https://github.com/Revertron/wyrd"),
|
||||||
|
DNSInfo(InetAddress.getByName("[303:8b1a::53]"),
|
||||||
|
"RU",
|
||||||
|
"DNS implementation for Yggdrasil. https://github.com/Revertron/wyrd")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_dns_list)
|
||||||
|
setSupportActionBar(findViewById(R.id.toolbar))
|
||||||
|
findViewById<FloatingActionButton>(R.id.fab).setOnClickListener { view ->
|
||||||
|
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
|
||||||
|
.setAction("Action", null).show()
|
||||||
|
}
|
||||||
|
var extras = intent.extras
|
||||||
|
var dnsList = findViewById<ListView>(R.id.dnsList)
|
||||||
|
var adapter = SelectDNSInfoListAdapter(this, arrayListOf(), mutableSetOf())
|
||||||
|
dnsList.adapter = adapter
|
||||||
|
thread(start = true) {
|
||||||
|
try {
|
||||||
|
var cd = MainActivity.deserializeStringList2DNSInfoSet(
|
||||||
|
extras!!.getStringArrayList(MainActivity.DNS_LIST)!!
|
||||||
|
)
|
||||||
|
for (d in cd) {
|
||||||
|
var ping = ping(d.address, 53)
|
||||||
|
d.ping = ping
|
||||||
|
}
|
||||||
|
for (dns in allDNS) {
|
||||||
|
if (cd.contains(dns)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var ping = ping(dns.address, 53)
|
||||||
|
dns.ping = ping
|
||||||
|
adapter.addItem(dns)
|
||||||
|
runOnUiThread(
|
||||||
|
Runnable
|
||||||
|
{
|
||||||
|
adapter.sort()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
|
// Inflate the menu; this adds items to the action bar if it is present.
|
||||||
|
menuInflater.inflate(R.menu.save, menu)
|
||||||
|
val item = menu.findItem(R.id.saveItem) as MenuItem
|
||||||
|
item.setActionView(R.layout.menu_save)
|
||||||
|
val saveButton = item
|
||||||
|
.actionView.findViewById<Button>(R.id.saveButton)
|
||||||
|
saveButton.setOnClickListener {
|
||||||
|
val result = Intent(this, MainActivity::class.java)
|
||||||
|
var adapter = findViewById<ListView>(R.id.dnsList).adapter as SelectDNSInfoListAdapter
|
||||||
|
val selectedDNS = adapter.getSelectedDNS()
|
||||||
|
if(selectedDNS.size>0) {
|
||||||
|
result.putExtra(MainActivity.DNS_LIST, MainActivity.serializeDNSInfoSet2StringList(selectedDNS))
|
||||||
|
setResult(Activity.RESULT_OK, result)
|
||||||
|
finish()
|
||||||
|
} else {
|
||||||
|
val text = "Select at least one DNS"
|
||||||
|
val duration = Toast.LENGTH_SHORT
|
||||||
|
val toast = Toast.makeText(applicationContext, text, duration)
|
||||||
|
toast.setGravity(Gravity.CENTER, 0, 0)
|
||||||
|
toast.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
@ -13,7 +13,9 @@ import android.widget.*
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
|
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.PeerInfoListAdapter
|
import io.github.chronosx88.yggdrasil.models.config.PeerInfoListAdapter
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
import kotlin.collections.HashSet
|
import kotlin.collections.HashSet
|
||||||
@ -31,9 +33,13 @@ class MainActivity : AppCompatActivity() {
|
|||||||
const val STATUS_STOP = 9
|
const val STATUS_STOP = 9
|
||||||
const val IPv6: String = "IPv6"
|
const val IPv6: String = "IPv6"
|
||||||
const val PEERS: String = "PEERS"
|
const val PEERS: String = "PEERS"
|
||||||
|
const val DNS: String = "DNS"
|
||||||
const val PEER_LIST_CODE = 1000
|
const val PEER_LIST_CODE = 1000
|
||||||
|
const val DNS_LIST_CODE = 2000
|
||||||
const val PEER_LIST = "PEERS_LIST"
|
const val PEER_LIST = "PEERS_LIST"
|
||||||
|
const val DNS_LIST = "DNS_LIST"
|
||||||
const val CURRENT_PEERS = "CURRENT_PEERS_v1.1"
|
const val CURRENT_PEERS = "CURRENT_PEERS_v1.1"
|
||||||
|
const val CURRENT_DNS = "CURRENT_DNS_v1.1"
|
||||||
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
|
||||||
@ -48,6 +54,16 @@ class MainActivity : AppCompatActivity() {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun deserializeStringList2DNSInfoSet(list: List<String>): MutableSet<DNSInfo> {
|
||||||
|
var gson = Gson()
|
||||||
|
var out = mutableSetOf<DNSInfo>()
|
||||||
|
for(s in list) {
|
||||||
|
out.add(gson.fromJson(s, DNSInfo::class.java))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun deserializeStringSet2PeerInfoSet(list: Set<String>): MutableSet<PeerInfo> {
|
fun deserializeStringSet2PeerInfoSet(list: Set<String>): MutableSet<PeerInfo> {
|
||||||
var gson = Gson()
|
var gson = Gson()
|
||||||
@ -58,6 +74,16 @@ class MainActivity : AppCompatActivity() {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun deserializeStringSet2DNSInfoSet(list: Set<String>): MutableSet<DNSInfo> {
|
||||||
|
var gson = Gson()
|
||||||
|
var out = mutableSetOf<DNSInfo>()
|
||||||
|
for(s in list) {
|
||||||
|
out.add(gson.fromJson(s, DNSInfo::class.java))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun serializePeerInfoSet2StringList(list: Set<PeerInfo>): ArrayList<String> {
|
fun serializePeerInfoSet2StringList(list: Set<PeerInfo>): ArrayList<String> {
|
||||||
var gson = Gson()
|
var gson = Gson()
|
||||||
@ -67,10 +93,21 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun serializeDNSInfoSet2StringList(list: Set<DNSInfo>): ArrayList<String> {
|
||||||
|
var gson = Gson()
|
||||||
|
var out = ArrayList<String>()
|
||||||
|
for(p in list) {
|
||||||
|
out.add(gson.toJson(p))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var startVpnFlag = false
|
private var startVpnFlag = false
|
||||||
private var currentPeers = setOf<PeerInfo>()
|
private var currentPeers = setOf<PeerInfo>()
|
||||||
|
private var currentDNS = setOf<DNSInfo>()
|
||||||
private var isStarted = false
|
private var isStarted = false
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@ -82,7 +119,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
PreferenceManager.getDefaultSharedPreferences(this.baseContext)
|
PreferenceManager.getDefaultSharedPreferences(this.baseContext)
|
||||||
currentPeers = deserializeStringSet2PeerInfoSet(preferences.getStringSet(CURRENT_PEERS, HashSet())!!)
|
currentPeers = deserializeStringSet2PeerInfoSet(preferences.getStringSet(CURRENT_PEERS, HashSet())!!)
|
||||||
|
|
||||||
val adapter = PeerInfoListAdapter(this, ArrayList(currentPeers))
|
val adapter = PeerInfoListAdapter(this, currentPeers.sortedWith(compareBy { it.ping }))
|
||||||
listView.adapter = adapter
|
listView.adapter = adapter
|
||||||
val editPeersButton = findViewById<Button>(R.id.edit)
|
val editPeersButton = findViewById<Button>(R.id.edit)
|
||||||
editPeersButton.setOnClickListener {
|
editPeersButton.setOnClickListener {
|
||||||
@ -90,10 +127,25 @@ class MainActivity : AppCompatActivity() {
|
|||||||
showToast("Service is running. Please stop service before edit Peers list")
|
showToast("Service is running. Please stop service before edit Peers list")
|
||||||
return@setOnClickListener
|
return@setOnClickListener
|
||||||
}
|
}
|
||||||
val intent = Intent(this, PeerListActivity::class.java)
|
val intent = Intent(this@MainActivity, PeerListActivity::class.java)
|
||||||
intent.putStringArrayListExtra(PEER_LIST, serializePeerInfoSet2StringList(currentPeers))
|
intent.putStringArrayListExtra(PEER_LIST, serializePeerInfoSet2StringList(currentPeers))
|
||||||
startActivityForResult(intent, PEER_LIST_CODE)
|
startActivityForResult(intent, PEER_LIST_CODE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val listViewDNS = findViewById<ListView>(R.id.dns)
|
||||||
|
currentDNS = deserializeStringSet2DNSInfoSet(preferences.getStringSet(CURRENT_DNS, HashSet())!!)
|
||||||
|
val adapterDns = DNSInfoListAdapter(this, currentDNS.sortedWith(compareBy { it.ping }))
|
||||||
|
listViewDNS.adapter = adapterDns
|
||||||
|
val editDnsButton = findViewById<Button>(R.id.editDNS)
|
||||||
|
editDnsButton.setOnClickListener {
|
||||||
|
if(!isStarted){
|
||||||
|
showToast("Service is not running. DNS ping will not be run")
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
val intent = Intent(this@MainActivity, DNSListActivity::class.java)
|
||||||
|
intent.putStringArrayListExtra(DNS_LIST, serializeDNSInfoSet2StringList(currentDNS))
|
||||||
|
startActivityForResult(intent, DNS_LIST_CODE)
|
||||||
|
}
|
||||||
if(intent.extras!==null) {
|
if(intent.extras!==null) {
|
||||||
startVpnFlag = intent.extras!!.getBoolean(START_VPN, false)
|
startVpnFlag = intent.extras!!.getBoolean(START_VPN, false)
|
||||||
}
|
}
|
||||||
@ -123,6 +175,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
|
||||||
if (requestCode == VPN_REQUEST_CODE && resultCode== Activity.RESULT_OK){
|
if (requestCode == VPN_REQUEST_CODE && resultCode== Activity.RESULT_OK){
|
||||||
if(currentPeers.isEmpty()){
|
if(currentPeers.isEmpty()){
|
||||||
showToast("No peers selected!")
|
showToast("No peers selected!")
|
||||||
@ -134,8 +187,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
intent.putExtra(PARAM_PINTENT, pi)
|
intent.putExtra(PARAM_PINTENT, pi)
|
||||||
intent.putExtra(COMMAND, START)
|
intent.putExtra(COMMAND, START)
|
||||||
intent.putStringArrayListExtra(PEERS, serializePeerInfoSet2StringList(currentPeers))
|
intent.putStringArrayListExtra(PEERS, serializePeerInfoSet2StringList(currentPeers))
|
||||||
|
intent.putStringArrayListExtra(DNS, serializeDNSInfoSet2StringList(currentDNS))
|
||||||
startService(intent)
|
startService(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requestCode == PEER_LIST_CODE && resultCode== Activity.RESULT_OK){
|
if (requestCode == PEER_LIST_CODE && resultCode== Activity.RESULT_OK){
|
||||||
if(data!!.extras!=null){
|
if(data!!.extras!=null){
|
||||||
var currentPeers = data.extras!!.getStringArrayList(PEER_LIST)
|
var currentPeers = data.extras!!.getStringArrayList(PEER_LIST)
|
||||||
@ -152,7 +207,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
PreferenceManager.getDefaultSharedPreferences(this.baseContext)
|
PreferenceManager.getDefaultSharedPreferences(this.baseContext)
|
||||||
preferences.edit().putStringSet(CURRENT_PEERS, HashSet(currentPeers)).apply()
|
preferences.edit().putStringSet(CURRENT_PEERS, HashSet(currentPeers)).apply()
|
||||||
if(isStarted){
|
if(isStarted){
|
||||||
//TODO implement UpdateConfig methon in native interface and apply peer changes
|
//TODO implement UpdateConfig method in native interface and apply peer changes
|
||||||
stopVpn()
|
stopVpn()
|
||||||
val i = baseContext.packageManager
|
val i = baseContext.packageManager
|
||||||
.getLaunchIntentForPackage(baseContext.packageName)
|
.getLaunchIntentForPackage(baseContext.packageName)
|
||||||
@ -165,6 +220,36 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (requestCode == DNS_LIST_CODE && resultCode== Activity.RESULT_OK){
|
||||||
|
if(data!!.extras!=null){
|
||||||
|
var currentDNS = data.extras!!.getStringArrayList(DNS_LIST)
|
||||||
|
if(currentDNS==null || currentDNS.size==0){
|
||||||
|
showToast("No DNS selected!")
|
||||||
|
} else {
|
||||||
|
this.currentDNS = deserializeStringList2DNSInfoSet(currentDNS)
|
||||||
|
val adapter = DNSInfoListAdapter(this, this.currentDNS.sortedWith(compareBy { it.ping }))
|
||||||
|
val listView = findViewById<ListView>(R.id.dns)
|
||||||
|
listView.adapter = adapter
|
||||||
|
//save to shared preferences
|
||||||
|
val preferences =
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(this.baseContext)
|
||||||
|
preferences.edit().putStringSet(CURRENT_DNS, HashSet(currentDNS)).apply()
|
||||||
|
if(isStarted){
|
||||||
|
//TODO implement UpdateConfig method in native interface and apply peer changes
|
||||||
|
stopVpn()
|
||||||
|
val i = baseContext.packageManager
|
||||||
|
.getLaunchIntentForPackage(baseContext.packageName)
|
||||||
|
i!!.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
|
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
i.putExtra(START_VPN, true)
|
||||||
|
finish()
|
||||||
|
startActivity(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
when (resultCode) {
|
when (resultCode) {
|
||||||
STATUS_START -> print("service started")
|
STATUS_START -> print("service started")
|
||||||
STATUS_FINISH -> {
|
STATUS_FINISH -> {
|
||||||
|
@ -18,6 +18,7 @@ import com.hbb20.CCPCountry
|
|||||||
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.SelectPeerInfoListAdapter
|
import io.github.chronosx88.yggdrasil.models.config.SelectPeerInfoListAdapter
|
||||||
|
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.ping
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -44,19 +45,6 @@ class PeerListActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ping(address: InetAddress, port:Int): Int {
|
|
||||||
val start = System.currentTimeMillis()
|
|
||||||
val socket = Socket()
|
|
||||||
try {
|
|
||||||
socket.connect(InetSocketAddress(address, port), 5000)
|
|
||||||
socket.close()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
//silently pass
|
|
||||||
return Int.MAX_VALUE
|
|
||||||
}
|
|
||||||
return (System.currentTimeMillis() - start).toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_peer_list)
|
setContentView(R.layout.activity_peer_list)
|
||||||
@ -67,8 +55,7 @@ class PeerListActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
var extras = intent.extras
|
var extras = intent.extras
|
||||||
var peerList = findViewById<ListView>(R.id.peerList)
|
var peerList = findViewById<ListView>(R.id.peerList)
|
||||||
var allPeers = arrayListOf<PeerInfo>()
|
var adapter = SelectPeerInfoListAdapter(this, arrayListOf(), mutableSetOf())
|
||||||
var adapter = SelectPeerInfoListAdapter(this, allPeers, mutableSetOf())
|
|
||||||
peerList.adapter = adapter
|
peerList.adapter = adapter
|
||||||
|
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
@ -103,7 +90,7 @@ class PeerListActivity : AppCompatActivity() {
|
|||||||
var ping = ping(address, url.port)
|
var ping = ping(address, url.port)
|
||||||
peerInfo.ping = ping
|
peerInfo.ping = ping
|
||||||
adapter.addItem(peerInfo)
|
adapter.addItem(peerInfo)
|
||||||
if(peerList.adapter.count % 5 == 0) {
|
if(adapter.count % 5 == 0) {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
adapter.sort()
|
adapter.sort()
|
||||||
}
|
}
|
||||||
@ -128,7 +115,7 @@ 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_peers, menu)
|
menuInflater.inflate(R.menu.save, 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
|
||||||
|
@ -7,6 +7,7 @@ import android.os.ParcelFileDescriptor
|
|||||||
import android.system.OsConstants
|
import android.system.OsConstants
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import dummy.ConduitEndpoint
|
import dummy.ConduitEndpoint
|
||||||
|
import io.github.chronosx88.yggdrasil.models.DNSInfo
|
||||||
import io.github.chronosx88.yggdrasil.models.PeerInfo
|
import io.github.chronosx88.yggdrasil.models.PeerInfo
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
@ -53,18 +54,21 @@ class YggdrasilTunService : VpnService() {
|
|||||||
}
|
}
|
||||||
if (intent?.getStringExtra(MainActivity.COMMAND) == MainActivity.START) {
|
if (intent?.getStringExtra(MainActivity.COMMAND) == MainActivity.START) {
|
||||||
val peers = MainActivity.deserializeStringList2PeerInfoSet(intent.getStringArrayListExtra(MainActivity.PEERS))
|
val peers = MainActivity.deserializeStringList2PeerInfoSet(intent.getStringArrayListExtra(MainActivity.PEERS))
|
||||||
|
val dns = MainActivity.deserializeStringList2DNSInfoSet(intent.getStringArrayListExtra(MainActivity.DNS))
|
||||||
val pi: PendingIntent = intent.getParcelableExtra(MainActivity.PARAM_PINTENT)
|
val pi: PendingIntent = intent.getParcelableExtra(MainActivity.PARAM_PINTENT)
|
||||||
ygg = Yggdrasil()
|
ygg = Yggdrasil()
|
||||||
setupTunInterface(pi, peers)
|
setupTunInterface(pi, peers, dns)
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.onStartCommand(intent, flags, startId)
|
return super.onStartCommand(intent, flags, startId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupTunInterface(pi: PendingIntent, peers: Set<PeerInfo>) {
|
private fun setupTunInterface(
|
||||||
|
pi: PendingIntent,
|
||||||
|
peers: Set<PeerInfo>,
|
||||||
|
dns: MutableSet<DNSInfo>
|
||||||
|
) {
|
||||||
pi.send(MainActivity.STATUS_START)
|
pi.send(MainActivity.STATUS_START)
|
||||||
val builder = Builder()
|
|
||||||
|
|
||||||
var configJson = Mobile.generateConfigJSON()
|
var configJson = Mobile.generateConfigJSON()
|
||||||
val gson = Gson()
|
val gson = Gson()
|
||||||
var config = gson.fromJson(String(configJson), Map::class.java).toMutableMap()
|
var config = gson.fromJson(String(configJson), Map::class.java).toMutableMap()
|
||||||
@ -74,12 +78,16 @@ class YggdrasilTunService : VpnService() {
|
|||||||
yggConduitEndpoint = ygg.startJSON(configJson)
|
yggConduitEndpoint = ygg.startJSON(configJson)
|
||||||
val address = ygg.addressString // hack for getting generic ipv6 string from NodeID
|
val address = ygg.addressString // hack for getting generic ipv6 string from NodeID
|
||||||
|
|
||||||
tunInterface = builder
|
var builder = Builder()
|
||||||
.addAddress(address, 7)
|
.addAddress(address, 7)
|
||||||
.allowFamily(OsConstants.AF_INET)
|
.allowFamily(OsConstants.AF_INET)
|
||||||
.setMtu(MAX_PACKET_SIZE)
|
.setMtu(MAX_PACKET_SIZE)
|
||||||
.establish()
|
if(dns.size>0){
|
||||||
|
for (d in dns){
|
||||||
|
builder.addDnsServer(d.address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tunInterface = builder.establish()
|
||||||
tunInputStream = FileInputStream(tunInterface!!.fileDescriptor)
|
tunInputStream = FileInputStream(tunInterface!!.fileDescriptor)
|
||||||
tunOutputStream = FileOutputStream(tunInterface!!.fileDescriptor)
|
tunOutputStream = FileOutputStream(tunInterface!!.fileDescriptor)
|
||||||
readCoroutine = GlobalScope.launch {
|
readCoroutine = GlobalScope.launch {
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
package io.github.chronosx88.yggdrasil.models
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.hbb20.CCPCountry
|
||||||
|
import com.hbb20.CountryCodePicker
|
||||||
|
import java.net.InetAddress
|
||||||
|
|
||||||
|
|
||||||
|
class DNSInfo {
|
||||||
|
|
||||||
|
constructor(address: InetAddress, countryCode: String, description: String){
|
||||||
|
this.address = address
|
||||||
|
this.countryCode = countryCode
|
||||||
|
this.description = description
|
||||||
|
}
|
||||||
|
|
||||||
|
var address: InetAddress
|
||||||
|
var countryCode: String
|
||||||
|
var description: String
|
||||||
|
var ping: Int = Int.MAX_VALUE
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "[" + address.toString().substring(1) + "]"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
return toString() == other.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCountry(context: Context): CCPCountry? {
|
||||||
|
return CCPCountry.getCountryForNameCodeFromLibraryMasterList(context, CountryCodePicker.Language.ENGLISH, countryCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package io.github.chronosx88.yggdrasil.models.config
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import io.github.chronosx88.yggdrasil.R
|
||||||
|
import io.github.chronosx88.yggdrasil.models.DNSInfo
|
||||||
|
|
||||||
|
|
||||||
|
class DNSInfoListAdapter(
|
||||||
|
context: Context,
|
||||||
|
allDNS: List<DNSInfo>
|
||||||
|
) : ArrayAdapter<DNSInfo?> (context, 0, allDNS) {
|
||||||
|
|
||||||
|
private val mContext: Context = context
|
||||||
|
private var allDNS: List<DNSInfo> = allDNS
|
||||||
|
|
||||||
|
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||||
|
var dnsInfoHolder = DNSInfoHolder()
|
||||||
|
var listItem: View? = convertView
|
||||||
|
if (listItem == null) {
|
||||||
|
listItem = LayoutInflater.from(mContext).inflate(R.layout.host_list_item, parent, false)
|
||||||
|
dnsInfoHolder.countryFlag = listItem.findViewById(R.id.countryFlag) as ImageView
|
||||||
|
dnsInfoHolder.dnsInfoText = listItem.findViewById(R.id.hostInfoText) as TextView
|
||||||
|
listItem.tag = dnsInfoHolder
|
||||||
|
} else {
|
||||||
|
dnsInfoHolder = listItem.tag as DNSInfoHolder
|
||||||
|
}
|
||||||
|
val currentDNS = allDNS[position]
|
||||||
|
dnsInfoHolder.countryFlag.setImageResource(currentDNS.getCountry(mContext)!!.flagID)
|
||||||
|
dnsInfoHolder.dnsInfoText.text = currentDNS.toString()
|
||||||
|
return listItem!!
|
||||||
|
}
|
||||||
|
|
||||||
|
class DNSInfoHolder {
|
||||||
|
lateinit var countryFlag: ImageView
|
||||||
|
lateinit var dnsInfoText: TextView
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -5,12 +5,10 @@ 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.ArrayAdapter
|
||||||
import android.widget.CheckBox
|
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
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
|
||||||
import java.util.ArrayList
|
|
||||||
|
|
||||||
|
|
||||||
class PeerInfoListAdapter(
|
class PeerInfoListAdapter(
|
||||||
@ -25,17 +23,16 @@ class PeerInfoListAdapter(
|
|||||||
var peerInfoHolder = PeerInfoHolder()
|
var peerInfoHolder = PeerInfoHolder()
|
||||||
var listItem: View? = convertView
|
var listItem: View? = convertView
|
||||||
if (listItem == null) {
|
if (listItem == null) {
|
||||||
listItem = LayoutInflater.from(mContext).inflate(R.layout.peers_list_item, parent, false)
|
listItem = LayoutInflater.from(mContext).inflate(R.layout.host_list_item, parent, false)
|
||||||
peerInfoHolder.countryFlag = listItem.findViewById(R.id.countryFlag) as ImageView
|
peerInfoHolder.countryFlag = listItem.findViewById(R.id.countryFlag) as ImageView
|
||||||
peerInfoHolder.peerInfoText = listItem.findViewById(R.id.peerInfoText) as TextView
|
peerInfoHolder.peerInfoText = listItem.findViewById(R.id.hostInfoText) as TextView
|
||||||
listItem.tag = peerInfoHolder
|
listItem.tag = peerInfoHolder
|
||||||
} else {
|
} else {
|
||||||
peerInfoHolder = listItem.tag as PeerInfoHolder
|
peerInfoHolder = listItem.tag as PeerInfoHolder
|
||||||
}
|
}
|
||||||
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()
|
peerInfoHolder.peerInfoText.text = currentPeer.toString()
|
||||||
peerInfoHolder.peerInfoText.text = peerId
|
|
||||||
return listItem!!
|
return listItem!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,99 @@
|
|||||||
|
package io.github.chronosx88.yggdrasil.models.config
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.CheckBox
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import io.github.chronosx88.yggdrasil.R
|
||||||
|
import io.github.chronosx88.yggdrasil.models.DNSInfo
|
||||||
|
|
||||||
|
class SelectDNSInfoListAdapter(
|
||||||
|
context: Context,
|
||||||
|
allDNS: List<DNSInfo>,
|
||||||
|
currentDNS: MutableSet<DNSInfo>
|
||||||
|
) : ArrayAdapter<DNSInfo?> (context, 0, allDNS) {
|
||||||
|
|
||||||
|
private val mContext: Context = context
|
||||||
|
private var allDNS: MutableList<DNSInfo> = allDNS as MutableList<DNSInfo>
|
||||||
|
private var currentDNS: MutableSet<DNSInfo> = currentDNS
|
||||||
|
|
||||||
|
override fun getItem(position: Int): DNSInfo? {
|
||||||
|
return allDNS.get(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCount(): Int {
|
||||||
|
return allDNS.size
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||||
|
var dnsInfoHolder = DNSInfoHolder()
|
||||||
|
var listItem: View? = convertView
|
||||||
|
if (listItem == null) {
|
||||||
|
listItem = LayoutInflater.from(mContext).inflate(R.layout.host_list_item_edit, parent, false)
|
||||||
|
dnsInfoHolder.checkbox = listItem.findViewById(R.id.checkbox) as CheckBox
|
||||||
|
dnsInfoHolder.countryFlag = listItem.findViewById(R.id.countryFlag) as ImageView
|
||||||
|
dnsInfoHolder.dnsInfoText = listItem.findViewById(R.id.hostInfoText) as TextView
|
||||||
|
dnsInfoHolder.ping = listItem.findViewById(R.id.ping) as TextView
|
||||||
|
listItem.tag = dnsInfoHolder
|
||||||
|
} else {
|
||||||
|
dnsInfoHolder = listItem.tag as DNSInfoHolder
|
||||||
|
}
|
||||||
|
val currentDNS = allDNS[position]
|
||||||
|
dnsInfoHolder.countryFlag.setImageResource(currentDNS.getCountry(mContext)!!.flagID)
|
||||||
|
val dnsId = currentDNS.toString()
|
||||||
|
if(currentDNS.ping == Int.MAX_VALUE){
|
||||||
|
dnsInfoHolder.dnsInfoText.text = dnsId
|
||||||
|
dnsInfoHolder.ping.text=""
|
||||||
|
dnsInfoHolder.dnsInfoText.setTextColor(Color.GRAY)
|
||||||
|
} else {
|
||||||
|
dnsInfoHolder.dnsInfoText.text = dnsId
|
||||||
|
dnsInfoHolder.ping.text = currentDNS.ping.toString() + " ms"
|
||||||
|
dnsInfoHolder.dnsInfoText.setTextColor(Color.WHITE)
|
||||||
|
}
|
||||||
|
dnsInfoHolder.checkbox.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
if(isChecked){
|
||||||
|
if(!this.currentDNS.contains(currentDNS)){
|
||||||
|
this.currentDNS.add(currentDNS)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(this.currentDNS.contains(currentDNS)){
|
||||||
|
this.currentDNS.remove(currentDNS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dnsInfoHolder.checkbox.isChecked = this.currentDNS.contains(currentDNS)
|
||||||
|
return listItem!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSelectedDNS(): Set<DNSInfo> {
|
||||||
|
return currentDNS
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addItem(peerInfo: DNSInfo){
|
||||||
|
allDNS.add(peerInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addAll(index: Int, peerInfo: ArrayList<DNSInfo>){
|
||||||
|
currentDNS.addAll(peerInfo)
|
||||||
|
allDNS.removeAll(peerInfo)
|
||||||
|
allDNS.addAll(index, peerInfo)
|
||||||
|
this.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sort(){
|
||||||
|
allDNS = ArrayList(allDNS.sortedWith(compareBy { it.ping }))
|
||||||
|
this.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
class DNSInfoHolder {
|
||||||
|
lateinit var checkbox: CheckBox
|
||||||
|
lateinit var countryFlag: ImageView
|
||||||
|
lateinit var dnsInfoText: TextView
|
||||||
|
lateinit var ping: TextView
|
||||||
|
}
|
||||||
|
}
|
@ -34,10 +34,10 @@ class SelectPeerInfoListAdapter(
|
|||||||
var peerInfoHolder = PeerInfoHolder()
|
var peerInfoHolder = PeerInfoHolder()
|
||||||
var listItem: View? = convertView
|
var listItem: View? = convertView
|
||||||
if (listItem == null) {
|
if (listItem == null) {
|
||||||
listItem = LayoutInflater.from(mContext).inflate(R.layout.peers_list_item_edit, parent, false)
|
listItem = LayoutInflater.from(mContext).inflate(R.layout.host_list_item_edit, parent, false)
|
||||||
peerInfoHolder.checkbox = listItem.findViewById(R.id.checkbox) as CheckBox
|
peerInfoHolder.checkbox = listItem.findViewById(R.id.checkbox) as CheckBox
|
||||||
peerInfoHolder.countryFlag = listItem.findViewById(R.id.countryFlag) as ImageView
|
peerInfoHolder.countryFlag = listItem.findViewById(R.id.countryFlag) as ImageView
|
||||||
peerInfoHolder.peerInfoText = listItem.findViewById(R.id.peerInfoText) as TextView
|
peerInfoHolder.peerInfoText = listItem.findViewById(R.id.hostInfoText) as TextView
|
||||||
peerInfoHolder.ping = listItem.findViewById(R.id.ping) as TextView
|
peerInfoHolder.ping = listItem.findViewById(R.id.ping) as TextView
|
||||||
listItem.tag = peerInfoHolder
|
listItem.tag = peerInfoHolder
|
||||||
} else {
|
} else {
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
package io.github.chronosx88.yggdrasil.models.config
|
||||||
|
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.net.Socket
|
||||||
|
|
||||||
|
class Utils {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun ping(address: InetAddress, port:Int): Int {
|
||||||
|
val start = System.currentTimeMillis()
|
||||||
|
val socket = Socket()
|
||||||
|
try {
|
||||||
|
socket.connect(InetSocketAddress(address, port), 5000)
|
||||||
|
socket.close()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
print(address)
|
||||||
|
return Int.MAX_VALUE
|
||||||
|
}
|
||||||
|
return (System.currentTimeMillis() - start).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
40
app/src/main/res/layout/activity_dns_list.xml
Normal file
40
app/src/main/res/layout/activity_dns_list.xml
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?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=".DNSListActivity"
|
||||||
|
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_dns_list" />
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
android:id="@+id/fab"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|right"
|
||||||
|
android:layout_margin="@dimen/fab_margin"
|
||||||
|
app:backgroundTint="@color/green"
|
||||||
|
app:tint="@color/white"
|
||||||
|
app:borderWidth="0dp"
|
||||||
|
app:elevation="6dp"
|
||||||
|
app:fabSize="normal"
|
||||||
|
app:srcCompat="@android:drawable/ic_input_add"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
@ -131,7 +131,7 @@
|
|||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
<ListView
|
<ListView
|
||||||
android:id="@+id/dnsList"
|
android:id="@+id/dns"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
|
13
app/src/main/res/layout/content_dns_list.xml
Normal file
13
app/src/main/res/layout/content_dns_list.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/dnsList"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:divider="@color/dark_20"
|
||||||
|
android:dividerHeight="2px"/>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -15,7 +15,7 @@
|
|||||||
android:scaleType="fitStart"/>
|
android:scaleType="fitStart"/>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/peerInfoText"
|
android:id="@+id/hostInfoText"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
@ -31,7 +31,7 @@
|
|||||||
android:layout_toEndOf="@+id/frame"
|
android:layout_toEndOf="@+id/frame"
|
||||||
android:layout_centerInParent="true"/>
|
android:layout_centerInParent="true"/>
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/peerInfoText"
|
android:id="@+id/hostInfoText"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
@ -4,4 +4,5 @@
|
|||||||
<string name="disconnect_button">Disconnect</string>
|
<string name="disconnect_button">Disconnect</string>
|
||||||
<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>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
Reference in New Issue
Block a user