Merge pull request #21 from vikulin/master

Added Android 4 support (API 15)
This commit is contained in:
ChronosX88 2020-08-17 13:19:36 +04:00 committed by GitHub
commit addb9ac8a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 213 additions and 95 deletions

View File

@ -6,10 +6,10 @@ android {
compileSdkVersion 29 compileSdkVersion 29
defaultConfig { defaultConfig {
applicationId "io.github.chronosx88.yggdrasil" applicationId "io.github.chronosx88.yggdrasil"
minSdkVersion 21 minSdkVersion 15
targetSdkVersion 29 targetSdkVersion 29
versionCode 3 versionCode 4
versionName "1.3" versionName "1.4"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
project.ext.set("archivesBaseName", project.getParent().name+"-"+versionName) project.ext.set("archivesBaseName", project.getParent().name+"-"+versionName)
} }
@ -36,6 +36,12 @@ 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
}
} }
task ndkBuild(type: Exec) { task ndkBuild(type: Exec) {
@ -48,7 +54,6 @@ 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')
@ -59,7 +64,7 @@ dependencies {
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-alpha02'
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'

View File

@ -107,7 +107,7 @@ 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, 53)
@ -115,8 +115,8 @@ class DNSListActivity : AppCompatActivity() {
} 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()

View File

@ -3,7 +3,10 @@ package io.github.chronosx88.yggdrasil
import android.app.Activity import android.app.Activity
import android.app.ActivityManager import android.app.ActivityManager
import android.content.* import android.content.*
import android.net.ConnectivityManager
import android.net.Network
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
@ -11,9 +14,11 @@ import android.view.View
import android.widget.* import android.widget.*
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
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
import io.github.chronosx88.yggdrasil.models.config.NetworkUtils
import io.github.chronosx88.yggdrasil.models.config.PeerInfoListAdapter import io.github.chronosx88.yggdrasil.models.config.PeerInfoListAdapter
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.deserializePeerStringList2PeerInfoSet import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.deserializePeerStringList2PeerInfoSet
import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.deserializeStringList2DNSInfoSet import io.github.chronosx88.yggdrasil.models.config.Utils.Companion.deserializeStringList2DNSInfoSet
@ -22,11 +27,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
@ -62,12 +62,12 @@ class MainActivity : AppCompatActivity() {
@JvmStatic var isStarted = false @JvmStatic var isStarted = false
@JvmStatic var isCancelled = false @JvmStatic var isCancelled = false
@JvmStatic var address = "" @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)
@ -143,11 +143,60 @@ class MainActivity : AppCompatActivity() {
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)
}
} }
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 +206,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 +216,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)
@ -230,7 +279,7 @@ class MainActivity : AppCompatActivity() {
//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()
@ -254,7 +303,7 @@ class MainActivity : AppCompatActivity() {
//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()
} }
@ -332,8 +381,8 @@ class MainActivity : AppCompatActivity() {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
if (meshPeersReceiver != null){ if (networkStateReceiver != null){
unregisterReceiver(meshPeersReceiver); unregisterReceiver(networkStateReceiver);
} }
} }

View File

@ -1,7 +1,6 @@
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
@ -140,7 +139,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()
schemaInput.showSoftInputOnFocus = false if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
schemaInput.showSoftInputOnFocus = false
}
schemaInput.setOnFocusChangeListener { v, _ -> schemaInput.setOnFocusChangeListener { v, _ ->
if(schemaInput.isFocused) { if(schemaInput.isFocused) {
onClickSchemaList(v) onClickSchemaList(v)
@ -159,8 +160,20 @@ 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)

View File

@ -31,35 +31,29 @@ import java.net.Inet6Address
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 var scope: CoroutineScope? = 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 +61,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 +78,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()
.allowFamily(OsConstants.AF_INET) .addAddress(address, 7)
.allowBypass() .allowFamily(OsConstants.AF_INET)
.setMtu(MAX_PACKET_SIZE) .allowBypass()
.setBlocking(true)
.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 +101,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(
@ -132,7 +127,7 @@ class YggdrasilTunService : VpnService() {
val job = SupervisorJob() val job = SupervisorJob()
scope = CoroutineScope(Dispatchers.Default + job) scope = CoroutineScope(Dispatchers.Default + job)
scope!!.launch { scope!!.launch {
val buffer = ByteArray(2048) val buffer = ByteArray(MAX_PACKET_SIZE)
while (!isClosed) { while (!isClosed) {
readPacketsFromTun(yggConduitEndpoint, buffer) readPacketsFromTun(yggConduitEndpoint, buffer)
} }
@ -148,7 +143,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,
@ -219,12 +214,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 +225,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()
} }
@ -244,10 +235,9 @@ class YggdrasilTunService : VpnService() {
private fun stopVpn(pi: PendingIntent?) { private fun stopVpn(pi: PendingIntent?) {
isClosed = true; isClosed = true;
scope!!.coroutineContext.cancelChildren() 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,14 +255,17 @@ 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
val networks = cm.allNetworks if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
for (network in networks) { val networks = cm.allNetworks
val linkProperties = cm.getLinkProperties(network)
if(linkProperties!=null) { for (network in networks) {
val routes = linkProperties.routes val linkProperties = cm.getLinkProperties(network)
for (route in routes) { if(linkProperties!=null) {
if (route.isDefaultRoute && route.gateway is Inet6Address) { val routes = linkProperties.routes
return true for (route in routes) {
if (route.isDefaultRoute && route.gateway is Inet6Address) {
return true
}
} }
} }
} }
@ -280,27 +273,29 @@ class YggdrasilTunService : VpnService() {
return false return false
} }
private fun foregroundNotification(text: String): Notification? { private fun foregroundNotification(FOREGROUND_ID: Int, text: String) {
val channelId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channelId =
createNotificationChannel(TAG, "Yggdrasil service") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
} else { createNotificationChannel(TAG, "Yggdrasil service")
// If earlier version channel ID is not used } else {
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) // If earlier version channel ID is not used
"" // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
} ""
var intent = Intent(this, MainActivity::class.java) }
var stackBuilder = TaskStackBuilder.create(this) var intent = Intent(this, MainActivity::class.java)
stackBuilder.addNextIntentWithParentStack(intent) var stackBuilder = TaskStackBuilder.create(this)
var pi = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT) stackBuilder.addNextIntentWithParentStack(intent)
val b = NotificationCompat.Builder(this, channelId) var pi = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
b.setOngoing(true) val b = NotificationCompat.Builder(this, channelId)
.setContentIntent(pi) b.setOngoing(true)
.setContentTitle(getString(R.string.app_name)) .setContentIntent(pi)
.setContentText(text) .setContentTitle(getString(R.string.app_name))
.setSmallIcon(R.mipmap.ic_launcher) .setContentText(text)
.setTicker(text) .setSmallIcon(R.mipmap.ic_launcher)
return b.build() .setTicker(text)
startForeground(FOREGROUND_ID, b.build())
}
} }
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)

View File

@ -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
}
}
}

View File

@ -27,7 +27,8 @@
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 <Switch
android:id="@+id/staticIP" android:id="@+id/staticIP"

View File

@ -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>

View File

@ -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"

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="SwitchTheme" parent="Theme.AppCompat.Light">
<item name="android:colorControlActivated">@color/green</item>
</style>
</resources>

View File

@ -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">

View File

@ -4,6 +4,7 @@ buildscript {
ext.kotlin_version = '1.3.72' ext.kotlin_version = '1.3.72'
repositories { repositories {
google() google()
mavenCentral()
jcenter() jcenter()
} }
dependencies { dependencies {