From 80394001852e8400fd2c3d30e441306957d32243 Mon Sep 17 00:00:00 2001 From: Pacien TRAN-GIRARD Date: Tue, 4 Jul 2017 18:51:57 +0200 Subject: Add status activity --- .../org/pacien/tincapp/activities/StartActivity.kt | 8 ++ .../pacien/tincapp/activities/StatusActivity.kt | 88 ++++++++++++++++++++++ .../java/org/pacien/tincapp/commands/Executor.kt | 11 +-- .../main/java/org/pacien/tincapp/commands/Tinc.kt | 2 +- .../org/pacien/tincapp/service/TincVpnService.kt | 44 +++++++++-- .../tincapp/service/VpnInterfaceConfiguraton.kt | 28 ++++--- 6 files changed, 151 insertions(+), 30 deletions(-) create mode 100644 app/src/main/java/org/pacien/tincapp/activities/StatusActivity.kt (limited to 'app/src/main/java/org') diff --git a/app/src/main/java/org/pacien/tincapp/activities/StartActivity.kt b/app/src/main/java/org/pacien/tincapp/activities/StartActivity.kt index c438be0..19d01e6 100644 --- a/app/src/main/java/org/pacien/tincapp/activities/StartActivity.kt +++ b/app/src/main/java/org/pacien/tincapp/activities/StartActivity.kt @@ -12,6 +12,7 @@ import kotlinx.android.synthetic.main.base.* import kotlinx.android.synthetic.main.page_start.* import org.pacien.tincapp.R import org.pacien.tincapp.context.AppPaths +import org.pacien.tincapp.service.TincVpnService /** * @author pacien @@ -29,6 +30,13 @@ class StartActivity : BaseActivity(), AdapterView.OnItemClickListener { return super.onCreateOptionsMenu(m) } + override fun onResume() { + super.onResume() + + if (TincVpnService.isConnected()) startActivity(Intent(this, StatusActivity::class.java) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)) + } + override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { PromptActivity.requestVpnPermission((view as TextView).text.toString()) } diff --git a/app/src/main/java/org/pacien/tincapp/activities/StatusActivity.kt b/app/src/main/java/org/pacien/tincapp/activities/StatusActivity.kt new file mode 100644 index 0000000..ca572ea --- /dev/null +++ b/app/src/main/java/org/pacien/tincapp/activities/StatusActivity.kt @@ -0,0 +1,88 @@ +package org.pacien.tincapp.activities + +import android.content.Intent +import android.os.Bundle +import android.support.v7.app.AlertDialog +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.TextView +import kotlinx.android.synthetic.main.base.* +import kotlinx.android.synthetic.main.dialog_text_monopsace.view.* +import kotlinx.android.synthetic.main.fragment_network_status_header.* +import kotlinx.android.synthetic.main.page_status.* +import org.pacien.tincapp.R +import org.pacien.tincapp.commands.Tinc +import org.pacien.tincapp.service.TincVpnService +import org.pacien.tincapp.service.VpnInterfaceConfiguration + +/** + * @author pacien + */ +class StatusActivity : BaseActivity(), AdapterView.OnItemClickListener { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + layoutInflater.inflate(R.layout.page_status, main_content) + writeContent() + } + + override fun onCreateOptionsMenu(m: Menu): Boolean { + menuInflater.inflate(R.menu.menu_status, m) + return super.onCreateOptionsMenu(m) + } + + override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + val nodeName = (view as TextView).text.toString() + val v = layoutInflater.inflate(R.layout.dialog_text_monopsace, main_content, false) + v.dialog_text_monospace.text = Tinc.info(TincVpnService.getCurrentNetName()!!, nodeName) + + AlertDialog.Builder(this) + .setTitle(R.string.title_node_info) + .setView(v) + .setPositiveButton(R.string.action_close) { _, _ -> /* nop */ } + .show() + } + + fun stopVpn(@Suppress("UNUSED_PARAMETER") i: MenuItem) { + TincVpnService.stopVpn() + startActivity(Intent(this, StartActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)) + finish() + } + + private fun TextView.setText(list: List) { + if (list.isNotEmpty()) text = list.joinToString("\n") + else text = getString(R.string.value_none) + } + + private fun getNodeNames() = Tinc.dumpNodes(TincVpnService.getCurrentNetName()!!).map { it.substringBefore(" ") } + + private fun writeContent() { + node_list.addHeaderView(layoutInflater.inflate(R.layout.fragment_network_status_header, node_list, false), null, false) + node_list.addFooterView(View(this), null, false) + node_list.emptyView = node_list_empty + node_list.onItemClickListener = this + node_list.adapter = ArrayAdapter(this, R.layout.fragment_list_item, getNodeNames()) + + text_network_name.text = TincVpnService.getCurrentNetName() ?: getString(R.string.value_none) + writeNetworkInfo(TincVpnService.getCurrentInterfaceCfg() ?: VpnInterfaceConfiguration()) + } + + + private fun writeNetworkInfo(cfg: VpnInterfaceConfiguration) { + text_network_ip_addresses.setText(cfg.addresses.map { it.toString() }) + text_network_routes.setText(cfg.routes.map { it.toString() }) + text_network_dns_servers.setText(cfg.dnsServers) + text_network_search_domains.setText(cfg.searchDomains) + text_network_allow_bypass.text = getString(if (cfg.allowBypass) R.string.value_yes else R.string.value_no) + + block_network_allowed_applications.visibility = if (cfg.allowedApplications.isNotEmpty()) View.VISIBLE else View.GONE + text_network_allowed_applications.setText(cfg.allowedApplications) + + block_network_disallowed_applications.visibility = if (cfg.disallowedApplications.isNotEmpty()) View.VISIBLE else View.GONE + text_network_disallowed_applications.setText(cfg.disallowedApplications) + } + +} diff --git a/app/src/main/java/org/pacien/tincapp/commands/Executor.kt b/app/src/main/java/org/pacien/tincapp/commands/Executor.kt index 160f0cd..c93de64 100644 --- a/app/src/main/java/org/pacien/tincapp/commands/Executor.kt +++ b/app/src/main/java/org/pacien/tincapp/commands/Executor.kt @@ -3,7 +3,6 @@ package org.pacien.tincapp.commands import java.io.BufferedReader import java.io.IOException import java.io.InputStreamReader -import java.util.* /** * @author pacien @@ -29,15 +28,7 @@ internal object Executor { fun call(cmd: Command): List { val proc = ProcessBuilder(cmd.asList()).start() val outputReader = BufferedReader(InputStreamReader(proc.inputStream)) - - var line: String? = outputReader.readLine() - val list = LinkedList() - while (line != null) { - line = outputReader.readLine() - list.add(line) - } - - return list + return outputReader.readLines() } } diff --git a/app/src/main/java/org/pacien/tincapp/commands/Tinc.kt b/app/src/main/java/org/pacien/tincapp/commands/Tinc.kt index 22cbe71..e8ebb21 100644 --- a/app/src/main/java/org/pacien/tincapp/commands/Tinc.kt +++ b/app/src/main/java/org/pacien/tincapp/commands/Tinc.kt @@ -30,7 +30,7 @@ object Tinc { } @Throws(IOException::class) - fun dumpNodes(netName: String, reachable: Boolean): List = + fun dumpNodes(netName: String, reachable: Boolean = false): List = Executor.call( if (reachable) newCommand(netName).withArguments("dump", "reachable", "nodes") else newCommand(netName).withArguments("dump", "nodes")) diff --git a/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt b/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt index 30e2956..7813601 100644 --- a/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt +++ b/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt @@ -3,6 +3,7 @@ package org.pacien.tincapp.service import android.app.Service import android.content.Intent import android.net.VpnService +import android.os.ParcelFileDescriptor import org.pacien.tincapp.BuildConfig import org.pacien.tincapp.commands.Tinc import org.pacien.tincapp.commands.Tincd @@ -11,48 +12,75 @@ import org.pacien.tincapp.context.AppPaths import org.pacien.tincapp.utils.applyIgnoringException import java.io.IOException + /** * @author pacien */ class TincVpnService : VpnService() { - private var netName: String? = null - override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { - startVpn(intent.getStringExtra(INTENT_EXTRA_NET_NAME)!!) + when (intent.getSerializableExtra(INTENT_EXTRA_ACTION)) { + Action.START -> startVpn(intent.getStringExtra(INTENT_EXTRA_NET_NAME)!!) + Action.STOP -> onDestroy() + } + return Service.START_STICKY } override fun onDestroy() = try { Tinc.stop(netName!!) + fd!!.close() } catch (e: IOException) { e.printStackTrace() } finally { netName = null + interfaceCfg = null + fd = null + super.onDestroy() } private fun startVpn(netName: String) { - if (netName == this.netName) onDestroy() - this.netName = netName + if (netName == TincVpnService.netName) onDestroy() + TincVpnService.netName = netName + TincVpnService.interfaceCfg = VpnInterfaceConfiguration(AppPaths.netConfFile(netName)) - val net = Builder().setSession(netName) - net.apply(VpnInterfaceConfiguration(AppPaths.netConfFile(netName))) + val net = Builder().setSession(netName).apply(TincVpnService.interfaceCfg!!) applyIgnoringException(net::addDisallowedApplication, BuildConfig.APPLICATION_ID) try { - Tincd.start(netName, net.establish().detachFd()) + fd = net.establish() + Tincd.start(netName, fd!!.fd) } catch (e: IOException) { e.printStackTrace() } } companion object { + + private val INTENT_EXTRA_ACTION = "action" private val INTENT_EXTRA_NET_NAME = "netName" + private enum class Action { START, STOP } + + private var netName: String? = null + private var interfaceCfg: VpnInterfaceConfiguration? = null + private var fd: ParcelFileDescriptor? = null + fun startVpn(netName: String) { App.getContext().startService(Intent(App.getContext(), TincVpnService::class.java) + .putExtra(INTENT_EXTRA_ACTION, Action.START) .putExtra(TincVpnService.INTENT_EXTRA_NET_NAME, netName)) } + + fun stopVpn() { + App.getContext().startService(Intent(App.getContext(), TincVpnService::class.java) + .putExtra(INTENT_EXTRA_ACTION, Action.STOP)) + } + + fun getCurrentNetName() = netName + fun getCurrentInterfaceCfg() = interfaceCfg + fun isConnected() = netName != null + } } diff --git a/app/src/main/java/org/pacien/tincapp/service/VpnInterfaceConfiguraton.kt b/app/src/main/java/org/pacien/tincapp/service/VpnInterfaceConfiguraton.kt index 520d68c..50ccb20 100644 --- a/app/src/main/java/org/pacien/tincapp/service/VpnInterfaceConfiguraton.kt +++ b/app/src/main/java/org/pacien/tincapp/service/VpnInterfaceConfiguraton.kt @@ -25,19 +25,25 @@ private fun Configuration.getIntList(key: String): List = getList(Int::clas data class CidrAddress(val address: String, val prefix: Int) { constructor(slashSeparated: String) : - this(slashSeparated.substringBefore("/"), Integer.parseInt(slashSeparated.substringAfter("/"))) + this(slashSeparated.substringBefore(SEPARATOR), Integer.parseInt(slashSeparated.substringAfter(SEPARATOR))) + + override fun toString() = address + SEPARATOR + prefix + + companion object { + private val SEPARATOR = "/" + } } -data class VpnInterfaceConfiguration(val addresses: List, - val routes: List, - val dnsServers: List, - val searchDomains: List, - val allowedApplications: List, - val disallowedApplications: List, - val allowedFamilies: List, - val allowBypass: Boolean, - val blocking: Boolean, - val mtu: Int?) { +data class VpnInterfaceConfiguration(val addresses: List = emptyList(), + val routes: List = emptyList(), + val dnsServers: List = emptyList(), + val searchDomains: List = emptyList(), + val allowedApplications: List = emptyList(), + val disallowedApplications: List = emptyList(), + val allowedFamilies: List = emptyList(), + val allowBypass: Boolean = false, + val blocking: Boolean = false, + val mtu: Int? = null) { constructor(cfg: Configuration) : this( cfg.getCidrList(KEY_ADDRESSES), -- cgit v1.2.3