Add Logcat Buffer to Telegram Tracker

This commit is contained in:
Nazar-Kutz 2020-10-05 13:03:18 +03:00
parent cdc903e3db
commit 950bc1bd4a
9 changed files with 422 additions and 2 deletions

View file

@ -20,7 +20,7 @@
android:screenOrientation="unspecified"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name="net.osmand.TrackerLogcatActivity" />
<activity
android:name=".ui.MainActivity"
android:configChanges="orientation|screenSize"

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/card_bg_color">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="@dimen/action_bar_height">
<net.osmand.telegram.ui.views.TextViewEx
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:text="@string/logcat_buffer"
android:textColor="@color/app_bar_title_light"
android:textSize="@dimen/title_text_size"
app:typeface="@string/font_roboto_mono_bold"/>
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/screen_bg_light"
android:clipToPadding="false"
android:orientation="vertical"
android:scrollbars="vertical" />
</LinearLayout>

View file

@ -447,6 +447,50 @@
</LinearLayout>
<include layout="@layout/list_item_divider"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/card_bg_color">
<LinearLayout
android:id="@+id/logcat_row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:orientation="vertical">
<net.osmand.telegram.ui.views.TextViewEx
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:paddingLeft="@dimen/content_padding_standard"
android:paddingRight="@dimen/content_padding_standard"
android:text="@string/logcat_buffer"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/list_item_title_text_size"
app:firstBaselineToTopHeight="28sp"
app:typeface="@string/font_roboto_medium" />
<net.osmand.telegram.ui.views.TextViewEx
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/content_padding_standard"
android:paddingRight="@dimen/content_padding_standard"
android:text="@string/logcat_buffer_descr"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/list_item_description_text_size"
app:firstBaselineToTopHeight="20sp"
app:lastBaselineToBottomHeight="16sp"
app:typeface="@string/font_roboto_regular" />
</LinearLayout>
</FrameLayout>
<include layout="@layout/card_bottom_divider"/>
</LinearLayout>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:minHeight="@dimen/list_header_with_descr_height"
android:paddingLeft="@dimen/content_padding_standard"
android:paddingRight="@dimen/content_padding_standard"
android:textColor="?android:textColorSecondary"
android:textSize="@dimen/hint_text_size"
android:linksClickable="true"
android:lineSpacingMultiplier="@dimen/text_description_line_spacing_multiplier"
tools:text="Some long description"
android:paddingEnd="@dimen/content_padding_standard"
android:paddingStart="@dimen/content_padding_standard" />

View file

@ -27,7 +27,7 @@
<dimen name="dialog_welcome_title_top_margin">89dp</dimen>
<dimen name="list_header_height">48dp</dimen>
<dimen name="list_header_with_descr_height">42dp</dimen>
<dimen name="list_header_with_descr_height">44dp</dimen>
<dimen name="list_item_height">56dp</dimen>
<dimen name="list_item_height_min">48dp</dimen>

View file

@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="send_report">Send report</string>
<string name="logcat_buffer_descr">Check and share detailed logs of the app</string>
<string name="logcat_buffer">Logcat buffer</string>
<string name="shared_string_export">Export</string>
<string name="shared_string_error_short">ERR</string>
<string name="last_update_from_telegram_date">Last update from Telegram: %1$s</string>
<string name="last_response_date">Last response: %1$s</string>

View file

@ -0,0 +1,270 @@
package net.osmand
import android.os.AsyncTask
import android.os.Bundle
import android.view.*
import android.widget.ProgressBar
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import net.osmand.telegram.R
import net.osmand.telegram.TelegramApplication
import java.io.*
import java.lang.ref.WeakReference
import java.util.*
private const val LOGCAT_PATH = "logcat.log"
private const val MAX_BUFFER_LOG = 10000
private const val SHARE_ID = 0
private const val LEVEL_ID = 1
private val log = PlatformUtil.getLog(TrackerLogcatActivity::class.java)
class TrackerLogcatActivity : AppCompatActivity() {
private var logcatAsyncTask: LogcatAsyncTask? = null
private val logs: MutableList<String> = ArrayList()
private var adapter: LogcatAdapter? = null
private val LEVELS = arrayOf("D", "I", "W", "E")
private var filterLevel = 1
private var recyclerView: RecyclerView? = null
override fun onCreate(savedInstanceState: Bundle?) {
val app: TelegramApplication = getApplication() as TelegramApplication
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_tracker_logcat)
log.debug("text to test")
val toolbar = findViewById<Toolbar>(R.id.toolbar).apply {
navigationIcon = app.uiUtils.getThemedIcon(R.drawable.ic_arrow_back)
setNavigationOnClickListener { onBackPressed() }
}
setSupportActionBar(toolbar)
setupIntermediateProgressBar()
adapter = LogcatAdapter()
recyclerView = findViewById<View>(R.id.recycler_view) as RecyclerView?
recyclerView!!.layoutManager = LinearLayoutManager(this)
recyclerView!!.adapter = adapter
}
protected fun setupIntermediateProgressBar() {
val progressBar = ProgressBar(this)
progressBar.visibility = View.GONE
progressBar.isIndeterminate = true
val supportActionBar = supportActionBar
if (supportActionBar != null) {
supportActionBar.setDisplayShowCustomEnabled(true)
supportActionBar.customView = progressBar
setSupportProgressBarIndeterminateVisibility(false)
}
}
override fun setSupportProgressBarIndeterminateVisibility(visible: Boolean) {
val supportActionBar = supportActionBar
if (supportActionBar != null) {
supportActionBar.customView.visibility = if (visible) View.VISIBLE else View.GONE
}
}
override fun onResume() {
super.onResume()
startLogcatAsyncTask()
}
override fun onPause() {
super.onPause()
stopLogcatAsyncTask()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
val app: TelegramApplication = applicationContext as TelegramApplication
val share: MenuItem = menu.add(0, SHARE_ID, 0, R.string.shared_string_export)
share.icon = app.uiUtils.getThemedIcon(R.drawable.ic_action_share)
share.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
val level = menu.add(0, LEVEL_ID, 0, "")
level.title = getFilterLevel()
level.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
return super.onCreateOptionsMenu(menu)
}
private fun getFilterLevel(): String {
return "*:" + LEVELS[filterLevel]
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val itemId = item.itemId
when (itemId) {
android.R.id.home -> {
finish()
return true
}
LEVEL_ID -> {
filterLevel++
if (filterLevel >= LEVELS.size) {
filterLevel = 0
}
item.title = getFilterLevel()
stopLogcatAsyncTask()
logs.clear()
adapter!!.notifyDataSetChanged()
startLogcatAsyncTask()
return true
}
SHARE_ID -> {
startSaveLogsAsyncTask()
return true
}
}
return false
}
private fun startSaveLogsAsyncTask() {
val saveLogsAsyncTask = SaveLogsAsyncTask(this, logs)
saveLogsAsyncTask.execute()
}
private fun startLogcatAsyncTask() {
logcatAsyncTask = LogcatAsyncTask(this, getFilterLevel())
logcatAsyncTask!!.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
}
private fun stopLogcatAsyncTask() {
if (logcatAsyncTask != null && logcatAsyncTask!!.status == AsyncTask.Status.RUNNING) {
logcatAsyncTask!!.cancel(false)
logcatAsyncTask!!.stopLogging()
}
}
private inner class LogcatAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(viewGroup.context)
val itemView = inflater.inflate(R.layout.item_description_long, viewGroup, false) as TextView
itemView.gravity = Gravity.CENTER_VERTICAL
return LogViewHolder(itemView)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is LogViewHolder) {
val log = getLog(position)
holder.logTextView.text = log
}
}
override fun getItemCount(): Int {
return logs.size
}
private fun getLog(position: Int): String {
return logs[position]
}
private inner class LogViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val logTextView: TextView = itemView.findViewById(R.id.description)
}
}
class SaveLogsAsyncTask internal constructor(logcatActivity: TrackerLogcatActivity, logs: Collection<String>) : AsyncTask<Void?, String?, File?>() {
private val logcatActivity: WeakReference<TrackerLogcatActivity>
private val logs: Collection<String>
override fun onPreExecute() {
val activity = logcatActivity.get()
activity?.setSupportProgressBarIndeterminateVisibility(true)
}
override fun doInBackground(vararg voids: Void?): File {
val app: TelegramApplication = logcatActivity.get()?.applicationContext as TelegramApplication
val file = File(app.getExternalFilesDir(null), LOGCAT_PATH)
try {
if (file.exists()) {
file.delete()
}
val stringBuilder = StringBuilder()
for (log in logs) {
stringBuilder.append(log)
stringBuilder.append("\n")
}
if (file.parentFile.canWrite()) {
val writer = BufferedWriter(FileWriter(file, true))
writer.write(stringBuilder.toString())
writer.close()
}
} catch (e: Exception) {
log.error(e)
}
return file
}
override fun onPostExecute(file: File?) {
val activity = logcatActivity.get()
if (activity != null && file != null) {
val app: TelegramApplication = activity.applicationContext as TelegramApplication
activity.setSupportProgressBarIndeterminateVisibility(false)
app.sendCrashLog(file)
}
}
init {
this.logcatActivity = WeakReference(logcatActivity)
this.logs = logs
}
}
class LogcatAsyncTask internal constructor(logcatActivity: TrackerLogcatActivity?, filterLevel: String) : AsyncTask<Void?, String?, Void?>() {
private var processLogcat: Process? = null
private val logcatActivity: WeakReference<TrackerLogcatActivity?>
private val filterLevel: String
override fun doInBackground(vararg voids: Void?): Void? {
try {
val filter = android.os.Process.myPid().toString()
val command = arrayOf("logcat", filterLevel, "--pid=$filter", "-T", MAX_BUFFER_LOG.toString())
processLogcat = Runtime.getRuntime().exec(command)
val bufferedReader = BufferedReader(InputStreamReader(processLogcat?.inputStream))
var line: String?
while (bufferedReader.readLine().also { line = it } != null && logcatActivity.get() != null) {
if (isCancelled) {
break
}
publishProgress(line)
}
stopLogging()
} catch (e: IOException) { // ignore
} catch (e: Exception) {
log.error(e)
}
return null
}
override fun onProgressUpdate(vararg values: String?) {
if (values.size > 0 && !isCancelled) {
val activity = logcatActivity.get()
if (activity != null) {
val autoscroll = !activity.recyclerView!!.canScrollVertically(1)
for (s in values) {
if (s != null) {
activity.logs.add(s)
}
}
activity.adapter!!.notifyDataSetChanged()
if (autoscroll) {
activity.recyclerView!!.scrollToPosition(activity.logs.size - 1)
}
}
}
}
fun stopLogging() {
if (processLogcat != null) {
processLogcat!!.destroy()
}
}
init {
this.logcatActivity = WeakReference(logcatActivity)
this.filterLevel = filterLevel
}
}
}

View file

@ -3,16 +3,20 @@ package net.osmand.telegram
import android.app.Application
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.ConnectivityManager
import android.net.NetworkInfo
import android.os.Build
import android.os.Handler
import net.osmand.PlatformUtil
import net.osmand.TrackerLogcatActivity
import net.osmand.telegram.helpers.*
import net.osmand.telegram.helpers.OsmandAidlHelper.OsmandHelperListener
import net.osmand.telegram.helpers.OsmandAidlHelper.UpdatesListener
import net.osmand.telegram.notifications.NotificationHelper
import net.osmand.telegram.utils.AndroidUtils
import net.osmand.telegram.utils.UiUtils
import java.io.File
class TelegramApplication : Application() {
@ -200,4 +204,33 @@ class TelegramApplication : Application() {
fun runInUIThread(action: (() -> Unit), delay: Long) {
uiHandler.postDelayed(action, delay)
}
fun sendCrashLog(file: File) {
val intent = Intent(Intent.ACTION_SEND)
intent.putExtra(Intent.EXTRA_EMAIL, arrayOf("crash@osmand.net"))
intent.putExtra(Intent.EXTRA_STREAM, AndroidUtils.getUriForFile(this, file))
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.type = "vnd.android.cursor.dir/email"
intent.putExtra(Intent.EXTRA_SUBJECT, "OsmAnd bug")
val text = StringBuilder()
text.append("\nDevice : ").append(Build.DEVICE)
text.append("\nBrand : ").append(Build.BRAND)
text.append("\nModel : ").append(Build.MODEL)
text.append("\nProduct : ").append(Build.PRODUCT)
text.append("\nBuild : ").append(Build.DISPLAY)
text.append("\nVersion : ").append(Build.VERSION.RELEASE)
text.append("\nApp : ").append(getString(R.string.app_name_short))
try {
val info = packageManager.getPackageInfo(packageName, 0)
if (info != null) {
text.append("\nApk Version : ").append(info.versionName).append(" ").append(info.versionCode)
}
} catch (e: PackageManager.NameNotFoundException) {
PlatformUtil.getLog(TrackerLogcatActivity::class.java).error("", e)
}
intent.putExtra(Intent.EXTRA_TEXT, text.toString())
val chooserIntent = Intent.createChooser(intent, getString(R.string.send_report))
chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(chooserIntent)
}
}

View file

@ -14,6 +14,7 @@ import android.widget.*
import androidx.appcompat.widget.ListPopupWindow
import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat
import net.osmand.TrackerLogcatActivity
import net.osmand.telegram.R
import net.osmand.telegram.TelegramSettings
import net.osmand.telegram.TelegramSettings.ListPreference
@ -213,6 +214,12 @@ class SettingsDialogFragment : BaseDialogFragment() {
DisconnectTelegramBottomSheet.showInstance(childFragmentManager)
}
mainView.findViewById<View>(R.id.logcat_row).setOnClickListener {
val intent = Intent(activity, TrackerLogcatActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
app.startActivity(intent)
}
return mainView
}