Added saving of enabled state, added some fixes and refactorings.

This commit is contained in:
Revertron 2022-11-18 14:11:43 +01:00
parent 1152070fac
commit 631d321206
9 changed files with 133 additions and 40 deletions

View file

@ -4,6 +4,7 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<application <application
android:name=".GlobalApplication" android:name=".GlobalApplication"

View file

@ -9,6 +9,7 @@ import android.view.ContextThemeWrapper
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.widget.* import android.widget.*
import androidx.preference.PreferenceManager
import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputEditText
const val KEY_DNS_SERVERS = "dns_servers" const val KEY_DNS_SERVERS = "dns_servers"
@ -85,7 +86,7 @@ class DnsActivity : AppCompatActivity() {
enableChromeFix.toggle() enableChromeFix.toggle()
} }
preferences = androidx.preference.PreferenceManager.getDefaultSharedPreferences(this.baseContext) preferences = PreferenceManager.getDefaultSharedPreferences(this.baseContext)
val serverString = preferences.getString(KEY_DNS_SERVERS, "") val serverString = preferences.getString(KEY_DNS_SERVERS, "")
servers = if (serverString!!.isNotEmpty()) { servers = if (serverString!!.isNotEmpty()) {
serverString.split(",").toMutableList() serverString.split(",").toMutableList()

View file

@ -1,10 +1,15 @@
package eu.neilalexander.yggdrasil package eu.neilalexander.yggdrasil
import android.app.Application import android.app.*
import android.content.ComponentName import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Build import android.os.Build
import android.service.quicksettings.TileService import android.service.quicksettings.TileService
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
const val PREF_KEY_ENABLED = "enabled"
class GlobalApplication: Application(), YggStateReceiver.StateReceiver { class GlobalApplication: Application(), YggStateReceiver.StateReceiver {
private lateinit var config: ConfigurationProxy private lateinit var config: ConfigurationProxy
@ -15,6 +20,7 @@ class GlobalApplication: Application(), YggStateReceiver.StateReceiver {
super.onCreate() super.onCreate()
config = ConfigurationProxy(applicationContext) config = ConfigurationProxy(applicationContext)
val callback = NetworkStateCallback(this) val callback = NetworkStateCallback(this)
callback.register()
val receiver = YggStateReceiver(this) val receiver = YggStateReceiver(this)
receiver.register(this) receiver.register(this)
} }
@ -42,7 +48,53 @@ class GlobalApplication: Application(), YggStateReceiver.StateReceiver {
if (state != currentState) { if (state != currentState) {
val componentName = ComponentName(this, YggTileService::class.java) val componentName = ComponentName(this, YggTileService::class.java)
TileService.requestListeningState(this, componentName) TileService.requestListeningState(this, componentName)
if (state != State.Disabled) {
val notification = createServiceNotification(this, state)
val notificationManager: NotificationManager =
this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(SERVICE_NOTIFICATION_ID, notification)
}
currentState = state currentState = state
} }
} }
}
fun createServiceNotification(context: Context, state: State): Notification {
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
val channelId = "Foreground Service"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = context.getString(R.string.channel_name)
val descriptionText = context.getString(R.string.channel_description)
val importance = NotificationManager.IMPORTANCE_MIN
val channel = NotificationChannel(channelId, name, importance).apply {
description = descriptionText
}
// Register the channel with the system
val notificationManager: NotificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
val intent = Intent(context, MainActivity::class.java).apply {
this.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val text = when (state) {
State.Disabled -> context.getText(R.string.tile_disabled)
State.Enabled -> context.getText(R.string.tile_enabled)
State.Connected -> context.getText(R.string.tile_connected)
else -> context.getText(R.string.tile_disabled)
}
return NotificationCompat.Builder(context, channelId)
.setContentTitle(context.getText(R.string.app_name))
.setContentText(text)
.setSmallIcon(R.drawable.ic_tile_icon)
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_MIN)
.build()
} }

View file

@ -11,7 +11,9 @@ import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.edit
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager
import eu.neilalexander.yggdrasil.PacketTunnelProvider.Companion.STATE_INTENT import eu.neilalexander.yggdrasil.PacketTunnelProvider.Companion.STATE_INTENT
import mobile.Mobile import mobile.Mobile
import org.json.JSONArray import org.json.JSONArray
@ -76,6 +78,8 @@ class MainActivity : AppCompatActivity() {
startService(intent) startService(intent)
} }
} }
val preferences = PreferenceManager.getDefaultSharedPreferences(this.baseContext)
preferences.edit(commit = true) { putBoolean(PREF_KEY_ENABLED, isChecked) }
} }
val enableYggdrasilPanel = findViewById<TableRow>(R.id.enableYggdrasilPanel) val enableYggdrasilPanel = findViewById<TableRow>(R.id.enableYggdrasilPanel)
@ -123,7 +127,7 @@ class MainActivity : AppCompatActivity() {
LocalBroadcastManager.getInstance(this).registerReceiver( LocalBroadcastManager.getInstance(this).registerReceiver(
receiver, IntentFilter(STATE_INTENT) receiver, IntentFilter(STATE_INTENT)
) )
val preferences = androidx.preference.PreferenceManager.getDefaultSharedPreferences(this.baseContext) val preferences = PreferenceManager.getDefaultSharedPreferences(this.baseContext)
val serverString = preferences.getString(KEY_DNS_SERVERS, "") val serverString = preferences.getString(KEY_DNS_SERVERS, "")
if (serverString!!.isNotEmpty()) { if (serverString!!.isNotEmpty()) {
val servers = serverString.split(",") val servers = serverString.split(",")

View file

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

View file

@ -2,10 +2,12 @@ package eu.neilalexander.yggdrasil
import android.content.* import android.content.*
import android.net.VpnService import android.net.VpnService
import android.os.Build
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import android.system.OsConstants import android.system.OsConstants
import android.util.Log import android.util.Log
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager
import eu.neilalexander.yggdrasil.YggStateReceiver.Companion.YGG_STATE_INTENT import eu.neilalexander.yggdrasil.YggStateReceiver.Companion.YGG_STATE_INTENT
import mobile.Yggdrasil import mobile.Yggdrasil
import org.json.JSONArray import org.json.JSONArray
@ -16,6 +18,7 @@ import kotlin.concurrent.thread
private const val TAG = "PacketTunnelProvider" private const val TAG = "PacketTunnelProvider"
const val SERVICE_NOTIFICATION_ID = 1000
class PacketTunnelProvider: VpnService() { class PacketTunnelProvider: VpnService() {
companion object { companion object {
@ -55,6 +58,11 @@ class PacketTunnelProvider: VpnService() {
Log.d(TAG, "Intent is null") Log.d(TAG, "Intent is null")
return START_NOT_STICKY return START_NOT_STICKY
} }
val preferences = PreferenceManager.getDefaultSharedPreferences(this.baseContext)
if (!preferences.getBoolean(PREF_KEY_ENABLED, false)) {
Log.d(TAG, "Service is disabled")
return START_NOT_STICKY
}
return when (intent.action ?: ACTION_STOP) { return when (intent.action ?: ACTION_STOP) {
ACTION_STOP -> { ACTION_STOP -> {
Log.d(TAG, "Stopping...") Log.d(TAG, "Stopping...")
@ -89,6 +97,9 @@ class PacketTunnelProvider: VpnService() {
return return
} }
val notification = createServiceNotification(this, State.Enabled)
startForeground(SERVICE_NOTIFICATION_ID, notification)
Log.d(TAG, config.getJSON().toString()) Log.d(TAG, config.getJSON().toString())
yggdrasil.startJSON(config.getJSONByteArray()) yggdrasil.startJSON(config.getJSONByteArray())
@ -112,11 +123,11 @@ class PacketTunnelProvider: VpnService() {
// If we don't set metered status of VPN it is considered as metered. // If we don't set metered status of VPN it is considered as metered.
// If we set it to false, then it will inherit this status from underlying network. // If we set it to false, then it will inherit this status from underlying network.
// See: https://developer.android.com/reference/android/net/VpnService.Builder#setMetered(boolean) // See: https://developer.android.com/reference/android/net/VpnService.Builder#setMetered(boolean)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
builder.setMetered(false) builder.setMetered(false)
} }
val preferences = androidx.preference.PreferenceManager.getDefaultSharedPreferences(this.baseContext) val preferences = PreferenceManager.getDefaultSharedPreferences(this.baseContext)
val serverString = preferences.getString(KEY_DNS_SERVERS, "") val serverString = preferences.getString(KEY_DNS_SERVERS, "")
if (serverString!!.isNotEmpty()) { if (serverString!!.isNotEmpty()) {
val servers = serverString.split(",") val servers = serverString.split(",")
@ -207,6 +218,7 @@ class PacketTunnelProvider: VpnService() {
intent.putExtra("state", STATE_DISABLED) intent.putExtra("state", STATE_DISABLED)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent) LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
stopForeground(true)
stopSelf() stopSelf()
} }
@ -236,10 +248,12 @@ class PacketTunnelProvider: VpnService() {
val intent = Intent(YGG_STATE_INTENT) val intent = Intent(YGG_STATE_INTENT)
var state = STATE_ENABLED var state = STATE_ENABLED
val dht = yggdrasil.dhtjson val dht = yggdrasil.dhtjson
val dhtState = JSONArray(dht) if (dht != null && dht != "null") {
val count = dhtState.length() val dhtState = JSONArray(dht)
if (count > 1) val count = dhtState.length()
state = STATE_CONNECTED if (count > 1)
state = STATE_CONNECTED
}
intent.putExtra("state", state) intent.putExtra("state", state)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent) LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
lastStateUpdate = curTime lastStateUpdate = curTime

View file

@ -8,6 +8,8 @@ import android.service.quicksettings.Tile
import android.service.quicksettings.TileService import android.service.quicksettings.TileService
import android.util.Log import android.util.Log
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.content.edit
import androidx.preference.PreferenceManager
private const val TAG = "TileService" private const val TAG = "TileService"
@ -63,6 +65,11 @@ class YggTileService: TileService(), YggStateReceiver.StateReceiver {
override fun onClick() { override fun onClick() {
super.onClick() super.onClick()
// Saving new state
val preferences = PreferenceManager.getDefaultSharedPreferences(this.baseContext)
val enabled = preferences.getBoolean(PREF_KEY_ENABLED, false)
preferences.edit(commit = true) { putBoolean(PREF_KEY_ENABLED, !enabled) }
// Starting or stopping VPN service
val intent = Intent(this, PacketTunnelProvider::class.java) val intent = Intent(this, PacketTunnelProvider::class.java)
intent.action = PacketTunnelProvider.ACTION_TOGGLE intent.action = PacketTunnelProvider.ACTION_TOGGLE
startService(intent) startService(intent)
@ -71,12 +78,11 @@ class YggTileService: TileService(), YggStateReceiver.StateReceiver {
private fun updateTileState(state: State) { private fun updateTileState(state: State) {
val tile = qsTile ?: return val tile = qsTile ?: return
val oldState = tile.state val oldState = tile.state
tile.state = when (state) { val preferences = PreferenceManager.getDefaultSharedPreferences(this.baseContext)
State.Unknown -> Tile.STATE_UNAVAILABLE val enabled = preferences.getBoolean(PREF_KEY_ENABLED, false)
State.Disabled -> Tile.STATE_INACTIVE tile.state = when (enabled) {
State.Enabled -> Tile.STATE_ACTIVE false -> Tile.STATE_INACTIVE
State.Connected -> Tile.STATE_ACTIVE true -> Tile.STATE_ACTIVE
State.Reconnecting -> Tile.STATE_ACTIVE
} }
var changed = oldState != tile.state var changed = oldState != tile.state
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {

View file

@ -22,8 +22,8 @@
<string name="dns_one_server">1 сервер</string> <string name="dns_one_server">1 сервер</string>
<string name="dns_many_servers">%d сервера/серверов</string> <string name="dns_many_servers">%d сервера/серверов</string>
<string name="dns_remove_title">Убрать %s?</string> <string name="dns_remove_title">Убрать %s?</string>
<string name="main_no_connectivity">Нет подключения</string> <string name="main_no_connectivity">Включено (Нет подключения)</string>
<string name="main_enabled">Включено</string> <string name="main_enabled">Подключено</string>
<string name="main_disabled">Выключено</string> <string name="main_disabled">Выключено</string>
<string name="main_no_peers">Нет пиров</string> <string name="main_no_peers">Нет пиров</string>
<string name="main_one_peer">1 пир</string> <string name="main_one_peer">1 пир</string>
@ -66,10 +66,12 @@
<string name="reset_configuration">Сбросить настройки</string> <string name="reset_configuration">Сбросить настройки</string>
<string name="reset_configuration_hint">Сброс создаст полностью новые настройки. Это изменит ваш публичный ключ и адрес IP.</string> <string name="reset_configuration_hint">Сброс создаст полностью новые настройки. Это изменит ваш публичный ключ и адрес IP.</string>
<string name="tile_disabled">Выключено</string> <string name="tile_disabled">Выключено</string>
<string name="tile_enabled">Включено</string> <string name="tile_enabled">Включено (Нет подключения)</string>
<string name="tile_connected">Подключено</string> <string name="tile_connected">Подключено</string>
<string name="location_amsterdam">Амстердам, Нидерланды</string> <string name="location_amsterdam">Амстердам, Нидерланды</string>
<string name="location_prague">Прага, Чехия</string> <string name="location_prague">Прага, Чехия</string>
<string name="location_bratislava">Братислава, Словакия</string> <string name="location_bratislava">Братислава, Словакия</string>
<string name="location_buffalo">Баффало, США</string> <string name="location_buffalo">Баффало, США</string>
<string name="channel_name">Сервис VPN</string>
<string name="channel_description">Главный канал нотификаций сервиса</string>
</resources> </resources>

View file

@ -22,8 +22,8 @@
<string name="dns_one_server">1 server</string> <string name="dns_one_server">1 server</string>
<string name="dns_many_servers">%d server</string> <string name="dns_many_servers">%d server</string>
<string name="dns_remove_title">Remove %s?</string> <string name="dns_remove_title">Remove %s?</string>
<string name="main_no_connectivity">No connectivity</string> <string name="main_no_connectivity">Enabled (No connectivity)</string>
<string name="main_enabled">Enabled</string> <string name="main_enabled">Connected</string>
<string name="main_disabled">Not enabled</string> <string name="main_disabled">Not enabled</string>
<string name="main_no_peers">No peers</string> <string name="main_no_peers">No peers</string>
<string name="main_one_peer">1 peer</string> <string name="main_one_peer">1 peer</string>
@ -66,10 +66,12 @@
<string name="reset_configuration">Reset configuration</string> <string name="reset_configuration">Reset configuration</string>
<string name="reset_configuration_hint">Resetting will overwrite with newly generated configuration. Your public keys and IP address on the network will change.</string> <string name="reset_configuration_hint">Resetting will overwrite with newly generated configuration. Your public keys and IP address on the network will change.</string>
<string name="tile_disabled">Disabled</string> <string name="tile_disabled">Disabled</string>
<string name="tile_enabled">Enabled</string> <string name="tile_enabled">Enabled (No connectivity)</string>
<string name="tile_connected">Connected</string> <string name="tile_connected">Connected</string>
<string name="location_amsterdam">Amsterdam, NL</string> <string name="location_amsterdam">Amsterdam, NL</string>
<string name="location_prague">Prague, CZ</string> <string name="location_prague">Prague, CZ</string>
<string name="location_bratislava">Bratislava, SK</string> <string name="location_bratislava">Bratislava, SK</string>
<string name="location_buffalo">Buffalo, US</string> <string name="location_buffalo">Buffalo, US</string>
<string name="channel_name">VPN Service</string>
<string name="channel_description">Main channel for foreground notification</string>
</resources> </resources>