From 483e6634e0621d2100ae11cbcd8cba6d21a76c4e Mon Sep 17 00:00:00 2001
From: pacien
Date: Sun, 19 Aug 2018 18:04:58 +0200
Subject: Refactor log viewer activity
---
app/build.gradle | 1 +
app/src/main/AndroidManifest.xml | 2 +-
.../org/pacien/tincapp/activities/BaseActivity.kt | 8 +
.../pacien/tincapp/activities/ViewLogActivity.kt | 164 ---------------------
.../activities/configure/ConfigureActivity.kt | 2 +-
.../tincapp/activities/status/StatusActivity.kt | 2 +-
.../tincapp/activities/viewlog/LogLiveData.kt | 74 ++++++++++
.../tincapp/activities/viewlog/LogViewModel.kt | 33 +++++
.../tincapp/activities/viewlog/ViewLogActivity.kt | 125 ++++++++++++++++
app/src/main/res/layout/page_viewlog.xml | 38 -----
app/src/main/res/layout/view_log_activity.xml | 39 +++++
app/src/main/res/menu/menu_viewlog.xml | 8 +-
app/src/main/res/values/strings.xml | 10 +-
13 files changed, 293 insertions(+), 213 deletions(-)
delete mode 100644 app/src/main/java/org/pacien/tincapp/activities/ViewLogActivity.kt
create mode 100644 app/src/main/java/org/pacien/tincapp/activities/viewlog/LogLiveData.kt
create mode 100644 app/src/main/java/org/pacien/tincapp/activities/viewlog/LogViewModel.kt
create mode 100644 app/src/main/java/org/pacien/tincapp/activities/viewlog/ViewLogActivity.kt
delete mode 100644 app/src/main/res/layout/page_viewlog.xml
create mode 100644 app/src/main/res/layout/view_log_activity.xml
diff --git a/app/build.gradle b/app/build.gradle
index d4db5ee..50fdddc 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -76,6 +76,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'net.sourceforge.streamsupport:streamsupport-cfuture:1.6.3'
+ implementation "android.arch.lifecycle:extensions:1.1.1"
implementation "com.android.support:support-compat:27.1.1"
implementation 'com.android.support:design:27.1.1'
implementation 'com.google.zxing:android-integration:3.3.0'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2536db4..054438e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -64,7 +64,7 @@
diff --git a/app/src/main/java/org/pacien/tincapp/activities/BaseActivity.kt b/app/src/main/java/org/pacien/tincapp/activities/BaseActivity.kt
index af71544..a51d401 100644
--- a/app/src/main/java/org/pacien/tincapp/activities/BaseActivity.kt
+++ b/app/src/main/java/org/pacien/tincapp/activities/BaseActivity.kt
@@ -18,6 +18,7 @@
package org.pacien.tincapp.activities
+import android.content.Intent
import android.os.Bundle
import android.support.annotation.LayoutRes
import android.support.annotation.StringRes
@@ -68,6 +69,13 @@ abstract class BaseActivity : AppCompatActivity() {
super.onStop()
}
+ override fun getSupportActionBar() = super.getSupportActionBar()!!
+
+ fun startActivityChooser(target: Intent, title: String) {
+ val intentChooser = Intent.createChooser(target, title)
+ startActivity(intentChooser)
+ }
+
fun aboutDialog(@Suppress("UNUSED_PARAMETER") i: MenuItem) {
AlertDialog.Builder(this)
.setTitle(resources.getString(R.string.app_name))
diff --git a/app/src/main/java/org/pacien/tincapp/activities/ViewLogActivity.kt b/app/src/main/java/org/pacien/tincapp/activities/ViewLogActivity.kt
deleted file mode 100644
index f3f7e24..0000000
--- a/app/src/main/java/org/pacien/tincapp/activities/ViewLogActivity.kt
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Tinc App, an Android binding and user interface for the tinc mesh VPN daemon
- * Copyright (C) 2017-2018 Pacien TRAN-GIRARD
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package org.pacien.tincapp.activities
-
-import android.content.Intent
-import android.os.Bundle
-import android.view.Menu
-import android.view.MenuItem
-import android.view.View
-import android.widget.ScrollView
-import kotlinx.android.synthetic.main.base.*
-import kotlinx.android.synthetic.main.page_viewlog.*
-import org.pacien.tincapp.R
-import org.pacien.tincapp.commands.Executor
-import org.pacien.tincapp.commands.Tinc
-import org.pacien.tincapp.service.TincVpnService
-import java.util.*
-import kotlin.concurrent.timer
-
-/**
- * @author pacien
- */
-class ViewLogActivity : BaseActivity() {
- companion object {
- private const val LOG_LINES = 250
- private const val LOG_LEVEL = 5
- private const val NEW_LINE = "\n"
- private const val SPACED_NEW_LINE = "\n\n"
- private const val UPDATE_INTERVAL = 250L // ms
- private const val MIME_TYPE = "text/plain"
- }
-
- private val log = LinkedList()
- private var logUpdateTimer: Timer? = null
- private var logger: Process? = null
- private var toggleButton: MenuItem? = null
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- supportActionBar!!.setDisplayHomeAsUpEnabled(true)
- layoutInflater.inflate(R.layout.page_viewlog, main_content)
- toggleLogging(true)
- }
-
- override fun onCreateOptionsMenu(m: Menu): Boolean {
- menuInflater.inflate(R.menu.menu_viewlog, m)
- toggleButton = m.findItem(R.id.log_viewer_action_toggle)
- return super.onCreateOptionsMenu(m)
- }
-
- override fun onSupportNavigateUp(): Boolean {
- finish()
- return true
- }
-
- override fun onDestroy() {
- toggleLogging(false)
- super.onDestroy()
- }
-
- fun share(@Suppress("UNUSED_PARAMETER") menuItem: MenuItem) {
- synchronized(this) {
- val logFragment = log.joinToString(NEW_LINE)
- val shareIntent = Intent(Intent.ACTION_SEND)
- .setType(MIME_TYPE)
- .putExtra(Intent.EXTRA_TEXT, logFragment)
-
- startActivity(Intent.createChooser(shareIntent, resources.getString(R.string.menu_share_log)))
- }
- }
-
- fun toggleLogging(@Suppress("UNUSED_PARAMETER") menuItem: MenuItem) = toggleLogging(logger == null)
-
- private fun toggleLogging(enable: Boolean) {
- if (enable) {
- disableUserScroll()
- toggleButton?.setIcon(R.drawable.ic_pause_circle_outline_primary_24dp)
- startLogging()
- } else {
- enableUserScroll()
- toggleButton?.setIcon(R.drawable.ic_pause_circle_filled_primary_24dp)
- stopLogging()
- }
- }
-
- private fun startLogging(level: Int = LOG_LEVEL) {
- appendLog(resources.getString(R.string.message_log_level_set, level))
-
- TincVpnService.getCurrentNetName()?.let { netName ->
- Tinc.log(netName, level).let { process ->
- logger = process
- Executor.runAsyncTask { captureLog(process) }
- }
- logUpdateTimer = timer(period = UPDATE_INTERVAL, action = { printLog() })
- } ?: run {
- appendLog(resources.getString(R.string.message_no_daemon))
- toggleLogging(false)
- }
- }
-
- private fun stopLogging() {
- logger?.destroy()
- logger = null
- logUpdateTimer?.cancel()
- logUpdateTimer?.purge()
- logUpdateTimer = null
- appendLog(resources.getString(R.string.message_log_paused))
- printLog()
- }
-
- private fun captureLog(logger: Process) {
- logger.inputStream?.use { inputStream ->
- inputStream.bufferedReader().useLines { lines ->
- lines.forEach { appendLog(it) }
- }
- }
- }
-
- private fun appendLog(line: String) = synchronized(this) {
- if (log.size >= LOG_LINES) log.removeFirst()
- log.addLast(line)
- }
-
- private fun printLog() = synchronized(this) {
- log.joinToString(SPACED_NEW_LINE).let {
- logview_text.post {
- logview_text.text = it
- logview_frame.post { logview_frame.fullScroll(View.FOCUS_DOWN) }
- }
- }
- }
-
- private fun enableUserScroll() {
- logview_text.setTextIsSelectable(true)
- logview_frame.setState(true)
- }
-
- private fun disableUserScroll() {
- logview_text.setTextIsSelectable(false)
- logview_frame.setState(false)
- }
-
- private fun ScrollView.setState(enabled: Boolean) {
- if (enabled) setOnTouchListener(null) else setOnTouchListener { _, _ -> true }
- logview_frame.isSmoothScrollingEnabled = enabled
- logview_frame.isVerticalScrollBarEnabled = enabled
- }
-}
diff --git a/app/src/main/java/org/pacien/tincapp/activities/configure/ConfigureActivity.kt b/app/src/main/java/org/pacien/tincapp/activities/configure/ConfigureActivity.kt
index d154ff7..9c9be70 100644
--- a/app/src/main/java/org/pacien/tincapp/activities/configure/ConfigureActivity.kt
+++ b/app/src/main/java/org/pacien/tincapp/activities/configure/ConfigureActivity.kt
@@ -29,7 +29,7 @@ import org.pacien.tincapp.activities.BaseActivity
class ConfigureActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- supportActionBar!!.setDisplayHomeAsUpEnabled(true)
+ supportActionBar.setDisplayHomeAsUpEnabled(true)
layoutInflater.inflate(R.layout.configure_activity, main_content)
}
}
diff --git a/app/src/main/java/org/pacien/tincapp/activities/status/StatusActivity.kt b/app/src/main/java/org/pacien/tincapp/activities/status/StatusActivity.kt
index 2bf42ce..3125738 100644
--- a/app/src/main/java/org/pacien/tincapp/activities/status/StatusActivity.kt
+++ b/app/src/main/java/org/pacien/tincapp/activities/status/StatusActivity.kt
@@ -36,8 +36,8 @@ import kotlinx.android.synthetic.main.status_node_info_dialog.view.*
import org.pacien.tincapp.R
import org.pacien.tincapp.activities.BaseActivity
import org.pacien.tincapp.activities.StartActivity
-import org.pacien.tincapp.activities.ViewLogActivity
import org.pacien.tincapp.activities.common.ProgressModal
+import org.pacien.tincapp.activities.viewlog.ViewLogActivity
import org.pacien.tincapp.commands.Executor
import org.pacien.tincapp.commands.Tinc
import org.pacien.tincapp.extensions.Android.setElements
diff --git a/app/src/main/java/org/pacien/tincapp/activities/viewlog/LogLiveData.kt b/app/src/main/java/org/pacien/tincapp/activities/viewlog/LogLiveData.kt
new file mode 100644
index 0000000..f410a8c
--- /dev/null
+++ b/app/src/main/java/org/pacien/tincapp/activities/viewlog/LogLiveData.kt
@@ -0,0 +1,74 @@
+/*
+ * Tinc App, an Android binding and user interface for the tinc mesh VPN daemon
+ * Copyright (C) 2017-2018 Pacien TRAN-GIRARD
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.pacien.tincapp.activities.viewlog
+
+import android.arch.lifecycle.LiveData
+import org.pacien.tincapp.commands.Executor
+import org.pacien.tincapp.commands.Tinc
+import java.util.*
+import kotlin.concurrent.timer
+
+/**
+ * @author pacien
+ */
+class LogLiveData(private val netName: String, private val logLevel: Int, private val logLineSize: Int) : LiveData>() {
+ private val updateInterval = 250L // milliseconds
+ private val executor = Executor
+ private val log = LinkedList()
+ private var loggerProcess: Process? = null
+ private var logUpdateTimer: Timer? = null
+
+ override fun onActive() {
+ loggerProcess = startNewLogger()
+ logUpdateTimer = timer(period = updateInterval, action = { outputLog() })
+ }
+
+ override fun onInactive() {
+ loggerProcess?.destroy()
+ logUpdateTimer?.apply { cancel() }?.apply { purge() }
+ }
+
+ private fun startNewLogger(): Process {
+ val newProcess = Tinc.log(netName, logLevel)
+ executor.runAsyncTask { captureProcessOutput(newProcess) }
+ return newProcess
+ }
+
+ private fun captureProcessOutput(process: Process) {
+ process.inputStream?.use { inputStream ->
+ inputStream.bufferedReader().useLines { lines ->
+ lines.forEach { appendToLog(it) }
+ }
+ }
+ }
+
+ private fun appendToLog(line: String) {
+ synchronized(log) {
+ if (log.size >= logLineSize) log.removeFirst()
+ log.addLast(line)
+ }
+ }
+
+ private fun outputLog() {
+ synchronized(log) {
+ val logView = ArrayList(log)
+ postValue(logView)
+ }
+ }
+}
diff --git a/app/src/main/java/org/pacien/tincapp/activities/viewlog/LogViewModel.kt b/app/src/main/java/org/pacien/tincapp/activities/viewlog/LogViewModel.kt
new file mode 100644
index 0000000..d9f0017
--- /dev/null
+++ b/app/src/main/java/org/pacien/tincapp/activities/viewlog/LogViewModel.kt
@@ -0,0 +1,33 @@
+/*
+ * Tinc App, an Android binding and user interface for the tinc mesh VPN daemon
+ * Copyright (C) 2017-2018 Pacien TRAN-GIRARD
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.pacien.tincapp.activities.viewlog
+
+import android.arch.lifecycle.ViewModel
+import org.pacien.tincapp.service.TincVpnService
+
+/**
+ * @author pacien
+ */
+class LogViewModel : ViewModel() {
+ private val logLevelNumeric = 5
+ val logLevelText = "DEBUG"
+ val netName by lazy { TincVpnService.getCurrentNetName()!! }
+ val log by lazy { LogLiveData(netName, logLevelNumeric, 250) }
+ var logging = true
+}
diff --git a/app/src/main/java/org/pacien/tincapp/activities/viewlog/ViewLogActivity.kt b/app/src/main/java/org/pacien/tincapp/activities/viewlog/ViewLogActivity.kt
new file mode 100644
index 0000000..a4e2216
--- /dev/null
+++ b/app/src/main/java/org/pacien/tincapp/activities/viewlog/ViewLogActivity.kt
@@ -0,0 +1,125 @@
+/*
+ * Tinc App, an Android binding and user interface for the tinc mesh VPN daemon
+ * Copyright (C) 2017-2018 Pacien TRAN-GIRARD
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.pacien.tincapp.activities.viewlog
+
+import android.arch.lifecycle.Observer
+import android.arch.lifecycle.ViewModelProviders
+import android.content.Intent
+import android.os.Bundle
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import android.widget.ScrollView
+import kotlinx.android.synthetic.main.base.*
+import kotlinx.android.synthetic.main.view_log_activity.*
+import org.pacien.tincapp.R
+import org.pacien.tincapp.activities.BaseActivity
+
+/**
+ * @author pacien
+ */
+class ViewLogActivity : BaseActivity() {
+ private val viewModel by lazy { ViewModelProviders.of(this).get(LogViewModel::class.java) }
+ private val logObserver: Observer> = Observer { showLog(it) }
+ private var toggleButton: MenuItem? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ supportActionBar.setDisplayHomeAsUpEnabled(true)
+ layoutInflater.inflate(R.layout.view_log_activity, main_content)
+ enableLogging(viewModel.logging)
+ }
+
+ override fun onCreateOptionsMenu(m: Menu): Boolean {
+ menuInflater.inflate(R.menu.menu_viewlog, m)
+ toggleButton = m.findItem(R.id.log_viewer_action_toggle)
+ return super.onCreateOptionsMenu(m)
+ }
+
+ override fun onSupportNavigateUp(): Boolean {
+ finish()
+ return true
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ fun shareLog(m: MenuItem) {
+ val logSnippet = viewModel.log.value?.joinToString("\n")
+ val shareIntent = Intent(Intent.ACTION_SEND)
+ .setType("text/plain")
+ .putExtra(Intent.EXTRA_TEXT, logSnippet)
+
+ startActivityChooser(shareIntent, resources.getString(R.string.log_view_menu_share_log))
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ fun toggleLogging(m: MenuItem) =
+ enableLogging(!viewModel.logging)
+
+ private fun enableLogging(enable: Boolean) {
+ setLoggingStateSubtitle(enable)
+ setPauseButtonState(!enable)
+ enableScrolling(!enable)
+ viewModel.logging = enable
+
+ if (enable)
+ viewModel.log.observe(this, logObserver)
+ else
+ viewModel.log.removeObservers(this)
+ }
+
+ private fun showLog(logLines: List?) {
+ val logSnippet = logLines?.joinToString("\n\n") ?: ""
+
+ log_view_text.post {
+ log_view_text.text = logSnippet
+ log_view_frame.scrollToBottom()
+ }
+ }
+
+ private fun setLoggingStateSubtitle(enabled: Boolean) {
+ supportActionBar.subtitle = when (enabled) {
+ true -> getString(R.string.log_view_state_level_format, viewModel.logLevelText)
+ false -> getString(R.string.log_view_state_paused)
+ }
+ }
+
+ private fun setPauseButtonState(paused: Boolean) {
+ val iconRes = when (paused) {
+ true -> R.drawable.ic_pause_circle_filled_primary_24dp
+ false -> R.drawable.ic_pause_circle_outline_primary_24dp
+ }
+
+ toggleButton?.setIcon(iconRes)
+ }
+
+ private fun enableScrolling(enabled: Boolean) {
+ if (enabled)
+ log_view_frame.setOnTouchListener(null)
+ else
+ log_view_frame.setOnTouchListener { _, _ -> true }
+
+ log_view_frame.isSmoothScrollingEnabled = enabled
+ log_view_text.setTextIsSelectable(enabled)
+ log_view_frame.scrollToBottom()
+ }
+
+ private fun ScrollView.scrollToBottom() {
+ postDelayed({ fullScroll(View.FOCUS_DOWN) }, 50)
+ }
+}
diff --git a/app/src/main/res/layout/page_viewlog.xml b/app/src/main/res/layout/page_viewlog.xml
deleted file mode 100644
index 4eea176..0000000
--- a/app/src/main/res/layout/page_viewlog.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/view_log_activity.xml b/app/src/main/res/layout/view_log_activity.xml
new file mode 100644
index 0000000..da663e4
--- /dev/null
+++ b/app/src/main/res/layout/view_log_activity.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/menu/menu_viewlog.xml b/app/src/main/res/menu/menu_viewlog.xml
index 7b1ce6d..bdcdb99 100644
--- a/app/src/main/res/menu/menu_viewlog.xml
+++ b/app/src/main/res/menu/menu_viewlog.xml
@@ -21,21 +21,21 @@
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a27fab7..a9bf3ee 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -38,8 +38,6 @@
Configure
Disconnect
Show log
- Toggle logging
- Share log
Passphrase
@@ -74,8 +72,6 @@
Disconnecting VPN…
A passphrase is required to unlock the keyring.
Tinc daemon exited during startup:\n%1$s\nCheck the logs for more details.
- Log level set to %1$d.
- Logging paused.
Could not apply network interface configuration:\n%1$s
Could not bind network interface. Is another VPN running?
Invalid network name.
@@ -136,4 +132,10 @@
Loading…
Node info
Close
+
+
+ Log level: %s
+ Logging paused
+ Toggle logging
+ Share log
--
cgit v1.2.3