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 @@ + tools:context="org.pacien.tincapp.activities.viewlog.ViewLogActivity"> 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