Merge pull request #9948 from osmandapp/AddLogcatBufferTelegram

Add Logcat Buffer to Telegram Tracker
This commit is contained in:
Vitaliy 2020-10-05 22:11:00 +03:00 committed by GitHub
commit 677bd5c757
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 441 additions and 9 deletions

View file

@ -20,7 +20,7 @@
android:screenOrientation="unspecified" android:screenOrientation="unspecified"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity android:name=".ui.TrackerLogcatActivity" />
<activity <activity
android:name=".ui.MainActivity" android:name=".ui.MainActivity"
android:configChanges="orientation|screenSize" 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> </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"/> <include layout="@layout/card_bottom_divider"/>
</LinearLayout> </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_description_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,6 +27,7 @@
<dimen name="dialog_welcome_title_top_margin">89dp</dimen> <dimen name="dialog_welcome_title_top_margin">89dp</dimen>
<dimen name="list_header_height">48dp</dimen> <dimen name="list_header_height">48dp</dimen>
<dimen name="list_description_height">44dp</dimen>
<dimen name="list_header_with_descr_height">42dp</dimen> <dimen name="list_header_with_descr_height">42dp</dimen>
<dimen name="list_item_height">56dp</dimen> <dimen name="list_item_height">56dp</dimen>

View file

@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <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="shared_string_error_short">ERR</string>
<string name="last_update_from_telegram_date">Last update from Telegram: %1$s</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> <string name="last_response_date">Last response: %1$s</string>

View file

@ -3,16 +3,20 @@ package net.osmand.telegram
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.NetworkInfo import android.net.NetworkInfo
import android.os.Build import android.os.Build
import android.os.Handler import android.os.Handler
import net.osmand.PlatformUtil
import net.osmand.telegram.ui.TrackerLogcatActivity
import net.osmand.telegram.helpers.* import net.osmand.telegram.helpers.*
import net.osmand.telegram.helpers.OsmandAidlHelper.OsmandHelperListener import net.osmand.telegram.helpers.OsmandAidlHelper.OsmandHelperListener
import net.osmand.telegram.helpers.OsmandAidlHelper.UpdatesListener import net.osmand.telegram.helpers.OsmandAidlHelper.UpdatesListener
import net.osmand.telegram.notifications.NotificationHelper import net.osmand.telegram.notifications.NotificationHelper
import net.osmand.telegram.utils.AndroidUtils import net.osmand.telegram.utils.AndroidUtils
import net.osmand.telegram.utils.UiUtils import net.osmand.telegram.utils.UiUtils
import java.io.File
class TelegramApplication : Application() { class TelegramApplication : Application() {
@ -200,4 +204,33 @@ class TelegramApplication : Application() {
fun runInUIThread(action: (() -> Unit), delay: Long) { fun runInUIThread(action: (() -> Unit), delay: Long) {
uiHandler.postDelayed(action, delay) 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

@ -540,14 +540,24 @@ class TelegramSettings(private val app: TelegramApplication) {
if (initTime && initSending) { if (initTime && initSending) {
initializing = true initializing = true
} else { } else {
var waitingTimeError = false
val maxWaitingTime = WAITING_TDLIB_TIME * MAX_MESSAGES_IN_TDLIB_PER_CHAT * max(1, chatsCount) val maxWaitingTime = WAITING_TDLIB_TIME * MAX_MESSAGES_IN_TDLIB_PER_CHAT * max(1, chatsCount)
val textSharingError = !shareInfo.lastTextMessageHandled && currentTime - shareInfo.lastSendTextMessageTime > maxWaitingTime val textSharingWaitingTime = currentTime - shareInfo.lastSendTextMessageTime
val mapSharingError = !shareInfo.lastMapMessageHandled && currentTime - shareInfo.lastSendMapMessageTime > maxWaitingTime val mapSharingWaitingTime = currentTime - shareInfo.lastSendMapMessageTime
if (shareInfo.hasSharingError val textSharingError = !shareInfo.lastTextMessageHandled && textSharingWaitingTime > maxWaitingTime
|| (shareTypeValue == SHARE_TYPE_MAP_AND_TEXT && (textSharingError || mapSharingError)) val mapSharingError = !shareInfo.lastMapMessageHandled && mapSharingWaitingTime > maxWaitingTime
|| textSharingError && (shareTypeValue == SHARE_TYPE_TEXT) if ((shareTypeValue == SHARE_TYPE_MAP_AND_TEXT && (textSharingError || mapSharingError))
|| mapSharingError && (shareTypeValue == SHARE_TYPE_MAP) || textSharingError && (shareTypeValue == SHARE_TYPE_TEXT)
) { || mapSharingError && (shareTypeValue == SHARE_TYPE_MAP)) {
waitingTimeError = true
log.debug("Send chats error for share type \"$shareTypeValue\"" +
"\nMax waiting time: ${maxWaitingTime}s" +
"\nLast text message handled: ${shareInfo.lastTextMessageHandled}" +
"\nText sharing waiting time: ${textSharingWaitingTime}s" +
"\nLast map message handled: ${shareInfo.lastMapMessageHandled}" +
"\nMap sharing waiting time: ${mapSharingWaitingTime}s")
}
if (shareInfo.hasSharingError || waitingTimeError) {
sendChatsErrors = true sendChatsErrors = true
locationTime = max(shareInfo.lastTextSuccessfulSendTime, shareInfo.lastMapSuccessfulSendTime) locationTime = max(shareInfo.lastTextSuccessfulSendTime, shareInfo.lastMapSuccessfulSendTime)
chatsIds.add(shareInfo.chatId) chatsIds.add(shareInfo.chatId)

View file

@ -776,6 +776,7 @@ class TelegramHelper private constructor() {
client?.send(TdApi.CreatePrivateChat(userId, false)) { obj -> client?.send(TdApi.CreatePrivateChat(userId, false)) { obj ->
when (obj.constructor) { when (obj.constructor) {
TdApi.Error.CONSTRUCTOR -> { TdApi.Error.CONSTRUCTOR -> {
log.debug("createPrivateChatWithUser ERROR $obj")
val error = obj as TdApi.Error val error = obj as TdApi.Error
if (error.code != IGNORED_ERROR_CODE) { if (error.code != IGNORED_ERROR_CODE) {
shareInfo.hasSharingError = true shareInfo.hasSharingError = true
@ -969,7 +970,7 @@ class TelegramHelper private constructor() {
val messageType = if (isBot) MESSAGE_TYPE_BOT else MESSAGE_TYPE_TEXT val messageType = if (isBot) MESSAGE_TYPE_BOT else MESSAGE_TYPE_TEXT
when (obj.constructor) { when (obj.constructor) {
TdApi.Error.CONSTRUCTOR -> { TdApi.Error.CONSTRUCTOR -> {
log.debug("handleTextLocationMessageUpdate - ERROR") log.debug("handleTextLocationMessageUpdate - ERROR $obj")
val error = obj as TdApi.Error val error = obj as TdApi.Error
if (error.code != IGNORED_ERROR_CODE) { if (error.code != IGNORED_ERROR_CODE) {
shareInfo.hasSharingError = true shareInfo.hasSharingError = true

View file

@ -213,6 +213,12 @@ class SettingsDialogFragment : BaseDialogFragment() {
DisconnectTelegramBottomSheet.showInstance(childFragmentManager) 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 return mainView
} }

View file

@ -0,0 +1,271 @@
package net.osmand.telegram.ui
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.PlatformUtil
import net.osmand.telegram.R
import net.osmand.telegram.TelegramApplication
import java.io.*
import java.lang.ref.WeakReference
import java.util.*
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 lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
val app: TelegramApplication = getApplication() as TelegramApplication
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_tracker_logcat)
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
}
}
companion object {
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)
}
}