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 } }