From 819b3a44920e303a670928ee56e32b5ae02efca1 Mon Sep 17 00:00:00 2001 From: Pacien TRAN-GIRARD Date: Thu, 6 Jul 2017 21:16:01 +0200 Subject: Refresh network and node lists --- .../org/pacien/tincapp/activities/StartActivity.kt | 64 +++++++++---- .../pacien/tincapp/activities/StatusActivity.kt | 105 ++++++++++++++------- .../org/pacien/tincapp/utils/AndroidExtensions.kt | 23 +++++ .../java/org/pacien/tincapp/utils/FileObserver.kt | 16 ++++ app/src/main/res/layout/page_start.xml | 5 +- app/src/main/res/layout/page_status.xml | 5 +- 6 files changed, 166 insertions(+), 52 deletions(-) create mode 100644 app/src/main/java/org/pacien/tincapp/utils/AndroidExtensions.kt create mode 100644 app/src/main/java/org/pacien/tincapp/utils/FileObserver.kt 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 19d01e6..cecd474 100644 --- a/app/src/main/java/org/pacien/tincapp/activities/StartActivity.kt +++ b/app/src/main/java/org/pacien/tincapp/activities/StartActivity.kt @@ -2,6 +2,7 @@ package org.pacien.tincapp.activities import android.content.Intent import android.os.Bundle +import android.support.v4.widget.SwipeRefreshLayout import android.view.Menu import android.view.MenuItem import android.view.View @@ -13,16 +14,30 @@ import kotlinx.android.synthetic.main.page_start.* import org.pacien.tincapp.R import org.pacien.tincapp.context.AppPaths import org.pacien.tincapp.service.TincVpnService +import org.pacien.tincapp.utils.FileObserver +import org.pacien.tincapp.utils.setElements /** * @author pacien */ -class StartActivity : BaseActivity(), AdapterView.OnItemClickListener { +class StartActivity : BaseActivity(), AdapterView.OnItemClickListener, SwipeRefreshLayout.OnRefreshListener { + + private var networkListAdapter: ArrayAdapter? = null + private var confChangeObserver: FileObserver? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + + networkListAdapter = ArrayAdapter(this, R.layout.fragment_list_item) + confChangeObserver = FileObserver(AppPaths.confDir().absolutePath, FileObserver.CHANGE, { _, _ -> onRefresh() }) + layoutInflater.inflate(R.layout.page_start, main_content) - writeContent() + network_list_wrapper.setOnRefreshListener(this) + network_list.addHeaderView(layoutInflater.inflate(R.layout.fragment_network_list_header, network_list, false), null, false) + network_list.addFooterView(View(this), null, false) + network_list.emptyView = network_list_empty + network_list.adapter = networkListAdapter + network_list.onItemClickListener = this } override fun onCreateOptionsMenu(m: Menu): Boolean { @@ -30,27 +45,44 @@ class StartActivity : BaseActivity(), AdapterView.OnItemClickListener { return super.onCreateOptionsMenu(m) } - override fun onResume() { - super.onResume() + override fun onDestroy() { + confChangeObserver = null + networkListAdapter = null + super.onDestroy() + } - if (TincVpnService.isConnected()) startActivity(Intent(this, StatusActivity::class.java) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)) + override fun onStart() { + super.onRestart() + onRefresh() + confChangeObserver?.startWatching() } - override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { - PromptActivity.requestVpnPermission((view as TextView).text.toString()) + override fun onStop() { + super.onStop() + confChangeObserver?.stopWatching() } - fun openConfigureActivity(@Suppress("UNUSED_PARAMETER") i: MenuItem) { - startActivity(Intent(this, ConfigureActivity::class.java)) + override fun onResume() { + super.onResume() + if (TincVpnService.isConnected()) openStatusActivity() } - private fun writeContent() { - network_list.addHeaderView(layoutInflater.inflate(R.layout.fragment_network_list_header, network_list, false), null, false) - network_list.addFooterView(View(this), null, false) - network_list.emptyView = network_list_empty - network_list.adapter = ArrayAdapter(this, R.layout.fragment_list_item, AppPaths.confDir().list()) - network_list.onItemClickListener = this + override fun onRefresh() { + val networks = AppPaths.confDir().list().toList() + runOnUiThread { + networkListAdapter?.setElements(networks) + network_list_wrapper.isRefreshing = false + } } + override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) = + PromptActivity.requestVpnPermission((view as TextView).text.toString()) + + fun openConfigureActivity(@Suppress("UNUSED_PARAMETER") i: MenuItem) = + startActivity(Intent(this, ConfigureActivity::class.java)) + + fun openStatusActivity() = + startActivity(Intent(this, StatusActivity::class.java) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)) + } diff --git a/app/src/main/java/org/pacien/tincapp/activities/StatusActivity.kt b/app/src/main/java/org/pacien/tincapp/activities/StatusActivity.kt index ca572ea..b4ba7dd 100644 --- a/app/src/main/java/org/pacien/tincapp/activities/StatusActivity.kt +++ b/app/src/main/java/org/pacien/tincapp/activities/StatusActivity.kt @@ -2,6 +2,7 @@ package org.pacien.tincapp.activities import android.content.Intent import android.os.Bundle +import android.support.v4.widget.SwipeRefreshLayout import android.support.v7.app.AlertDialog import android.view.Menu import android.view.MenuItem @@ -17,16 +18,33 @@ import org.pacien.tincapp.R import org.pacien.tincapp.commands.Tinc import org.pacien.tincapp.service.TincVpnService import org.pacien.tincapp.service.VpnInterfaceConfiguration +import org.pacien.tincapp.utils.setElements +import org.pacien.tincapp.utils.setText +import java.util.* +import kotlin.concurrent.timerTask /** * @author pacien */ -class StatusActivity : BaseActivity(), AdapterView.OnItemClickListener { +class StatusActivity : BaseActivity(), AdapterView.OnItemClickListener, SwipeRefreshLayout.OnRefreshListener { + + private var nodeListAdapter: ArrayAdapter? = null + private var refreshTimer: Timer? = null + private var updateView: Boolean = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + + nodeListAdapter = ArrayAdapter(this, R.layout.fragment_list_item) + refreshTimer = Timer(true) + layoutInflater.inflate(R.layout.page_status, main_content) - writeContent() + node_list_wrapper.setOnRefreshListener(this) + 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 = nodeListAdapter } override fun onCreateOptionsMenu(m: Menu): Boolean { @@ -34,55 +52,78 @@ class StatusActivity : BaseActivity(), AdapterView.OnItemClickListener { 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() + override fun onDestroy() { + super.onDestroy() + refreshTimer?.cancel() + nodeListAdapter = null + refreshTimer = null } - fun stopVpn(@Suppress("UNUSED_PARAMETER") i: MenuItem) { - TincVpnService.stopVpn() - startActivity(Intent(this, StartActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)) - finish() + override fun onStart() { + super.onStart() + writeNetworkInfo(TincVpnService.getCurrentInterfaceCfg() ?: VpnInterfaceConfiguration()) + updateView = true + onRefresh() + updateNodeList() } - private fun TextView.setText(list: List) { - if (list.isNotEmpty()) text = list.joinToString("\n") - else text = getString(R.string.value_none) + override fun onStop() { + super.onStop() + updateView = false } - private fun getNodeNames() = Tinc.dumpNodes(TincVpnService.getCurrentNetName()!!).map { it.substringBefore(" ") } + override fun onRefresh() { + val nodes = getNodeNames() + runOnUiThread { + nodeListAdapter?.setElements(nodes) + node_list_wrapper.isRefreshing = false + } + } - 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()) + override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + val nodeName = (view as TextView).text.toString() + val dialogTextView = layoutInflater.inflate(R.layout.dialog_text_monopsace, main_content, false) + dialogTextView.dialog_text_monospace.text = Tinc.info(TincVpnService.getCurrentNetName()!!, nodeName) - text_network_name.text = TincVpnService.getCurrentNetName() ?: getString(R.string.value_none) - writeNetworkInfo(TincVpnService.getCurrentInterfaceCfg() ?: VpnInterfaceConfiguration()) + AlertDialog.Builder(this) + .setTitle(R.string.title_node_info) + .setView(dialogTextView) + .setPositiveButton(R.string.action_close) { _, _ -> /* nop */ } + .show() } - - private fun writeNetworkInfo(cfg: VpnInterfaceConfiguration) { + fun writeNetworkInfo(cfg: VpnInterfaceConfiguration) { + text_network_name.text = TincVpnService.getCurrentNetName() ?: getString(R.string.value_none) 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) } + fun updateNodeList() { + refreshTimer?.schedule(timerTask { + onRefresh() + if (updateView) updateNodeList() + }, REFRESH_RATE) + } + + fun stopVpn(@Suppress("UNUSED_PARAMETER") i: MenuItem) { + TincVpnService.stopVpn() + startActivity(Intent(this, StartActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)) + finish() + } + + companion object { + private val REFRESH_RATE = 5000L + + fun getNodeNames() = + if (TincVpnService.isConnected()) Tinc.dumpNodes(TincVpnService.getCurrentNetName()!!).map { it.substringBefore(" ") } + else emptyList() + } + } diff --git a/app/src/main/java/org/pacien/tincapp/utils/AndroidExtensions.kt b/app/src/main/java/org/pacien/tincapp/utils/AndroidExtensions.kt new file mode 100644 index 0000000..732b5b2 --- /dev/null +++ b/app/src/main/java/org/pacien/tincapp/utils/AndroidExtensions.kt @@ -0,0 +1,23 @@ +package org.pacien.tincapp.utils + +import android.widget.ArrayAdapter +import android.widget.TextView +import org.pacien.tincapp.R +import org.pacien.tincapp.context.App + +/** + * @author pacien + */ + +fun ArrayAdapter.setElements(list: Collection) { + setNotifyOnChange(false) + clear() + addAll(list) + notifyDataSetChanged() + setNotifyOnChange(true) +} + +fun TextView.setText(list: List) { + if (list.isNotEmpty()) text = list.joinToString("\n") + else text = App.getContext().getString(R.string.value_none) +} diff --git a/app/src/main/java/org/pacien/tincapp/utils/FileObserver.kt b/app/src/main/java/org/pacien/tincapp/utils/FileObserver.kt new file mode 100644 index 0000000..0370c47 --- /dev/null +++ b/app/src/main/java/org/pacien/tincapp/utils/FileObserver.kt @@ -0,0 +1,16 @@ +package org.pacien.tincapp.utils + +/** + * @author pacien + */ +class FileObserver(path: String, + mask: Int = android.os.FileObserver.ALL_EVENTS, + private val listener: (event: Int, path: String?) -> Unit) : android.os.FileObserver(path, mask) { + + override fun onEvent(event: Int, path: String?) = listener(event, path) + + companion object { + val CHANGE = CREATE or DELETE or MODIFY or MOVED_TO or MOVED_FROM + } + +} diff --git a/app/src/main/res/layout/page_start.xml b/app/src/main/res/layout/page_start.xml index 0eaf7b0..68bf78f 100644 --- a/app/src/main/res/layout/page_start.xml +++ b/app/src/main/res/layout/page_start.xml @@ -1,6 +1,7 @@ - - + diff --git a/app/src/main/res/layout/page_status.xml b/app/src/main/res/layout/page_status.xml index 22faaf1..cd80d0b 100644 --- a/app/src/main/res/layout/page_status.xml +++ b/app/src/main/res/layout/page_status.xml @@ -1,9 +1,10 @@ - - + -- cgit v1.2.3