diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 57e2cdc..2a1e97b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -63,6 +63,11 @@
android:exported="false"
android:screenOrientation="behind" />
+
+
+ *
+ * This file is Created by fankes on 2022/10/4.
+ */
+@file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
+
+package com.fankes.apperrorstracking.ui.activity.debug
+
+import android.app.Activity
+import android.content.Intent
+import android.view.ContextMenu
+import android.view.MenuItem
+import android.view.View
+import android.widget.AdapterView
+import androidx.core.view.isVisible
+import com.fankes.apperrorstracking.R
+import com.fankes.apperrorstracking.databinding.ActivitiyLoggerBinding
+import com.fankes.apperrorstracking.databinding.AdapterLoggerBinding
+import com.fankes.apperrorstracking.databinding.DiaLoggerFilterBinding
+import com.fankes.apperrorstracking.locale.LocaleString
+import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
+import com.fankes.apperrorstracking.utils.factory.*
+import com.fankes.apperrorstracking.utils.tool.FrameworkTool
+import com.highcapable.yukihookapi.hook.factory.current
+import com.highcapable.yukihookapi.hook.factory.dataChannel
+import com.highcapable.yukihookapi.hook.log.YukiHookLogger
+import com.highcapable.yukihookapi.hook.log.YukiLoggerData
+import java.io.ByteArrayOutputStream
+import java.io.PrintStream
+import java.text.SimpleDateFormat
+import java.util.*
+
+class LoggerActivity : BaseActivity() {
+
+ companion object {
+
+ /** 请求保存文件回调标识 */
+ private const val WRITE_REQUEST_CODE = 0
+ }
+
+ /** 回调适配器改变 */
+ private var onChanged: (() -> Unit)? = null
+
+ /** 过滤条件 */
+ private var filters = arrayListOf("D", "I", "W", "E")
+
+ /** 全部的调试日志数据 */
+ private val listData = ArrayList()
+
+ override fun onCreate() {
+ binding.titleBackIcon.setOnClickListener { finish() }
+ binding.refreshIcon.setOnClickListener { refreshData() }
+ binding.filterIcon.setOnClickListener {
+ showDialog {
+ title = LocaleString.filterByCondition
+ binding.configCheck0.isChecked = filters.any { it == "D" }
+ binding.configCheck1.isChecked = filters.any { it == "I" }
+ binding.configCheck2.isChecked = filters.any { it == "W" }
+ binding.configCheck3.isChecked = filters.any { it == "E" }
+ confirmButton {
+ filters.clear()
+ if (binding.configCheck0.isChecked) filters.add("D")
+ if (binding.configCheck1.isChecked) filters.add("I")
+ if (binding.configCheck2.isChecked) filters.add("W")
+ if (binding.configCheck3.isChecked) filters.add("E")
+ refreshData()
+ }
+ cancelButton()
+ }
+ }
+ binding.exportAllIcon.setOnClickListener {
+ runCatching {
+ startActivityForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
+ addCategory(Intent.CATEGORY_OPENABLE)
+ type = "*/application"
+ putExtra(Intent.EXTRA_TITLE, "app_errors_tracking_${System.currentTimeMillis().toUtcTime()}.log")
+ }, WRITE_REQUEST_CODE)
+ }.onFailure { toast(msg = "Start Android SAF failed") }
+ }
+ /** 设置列表元素和 Adapter */
+ binding.listView.apply {
+ bindAdapter {
+ onBindDatas { listData }
+ onBindViews { binding, position ->
+ listData[position].also { bean ->
+ binding.priorityText.apply {
+ text = bean.priority
+ setBackgroundResource(
+ when (bean.priority) {
+ "D" -> R.drawable.bg_logger_d_round
+ "I" -> R.drawable.bg_logger_i_round
+ "W" -> R.drawable.bg_logger_w_round
+ "E" -> R.drawable.bg_logger_e_round
+ else -> R.drawable.bg_logger_d_round
+ }
+ )
+ }
+ binding.messageText.text = bean.msg.format()
+ binding.timeText.text = bean.timestamp.format()
+ binding.throwableText.isVisible = bean.throwable != null
+ binding.throwableText.text = bean.throwable?.toStackTrace() ?: ""
+ }
+ }
+ }.apply { onChanged = { notifyDataSetChanged() } }
+ registerForContextMenu(this)
+ }
+ }
+
+ /** 更新列表数据 */
+ private fun refreshData() {
+ dataChannel(FrameworkTool.SYSTEM_FRAMEWORK_NAME).obtainLoggerInMemoryData {
+ listData.clear()
+ it.takeIf { e -> e.isNotEmpty() }?.reversed()?.filter { filters.any { e -> it.priority == e } }?.forEach { e -> listData.add(e) }
+ onChanged?.invoke()
+ binding.listView.post { binding.listView.setSelection(0) }
+ binding.exportAllIcon.isVisible = listData.isNotEmpty()
+ binding.listView.isVisible = listData.isNotEmpty()
+ binding.listNoDataView.isVisible = listData.isEmpty()
+ binding.listNoDataView.text = if (filters.size < 4) LocaleString.noListResult else LocaleString.noListData
+ }
+ }
+
+ /**
+ * 获取当前所有日志写出文件的格式
+ *
+ * 复制自 [YukiHookLogger.contents]
+ * @return [String]
+ */
+ private val loggerContents: String
+ get() {
+ var content = ""
+ listData.takeIf { it.isNotEmpty() }?.forEach {
+ val head = "${it.time} ------ "
+ content += "$head$it\n"
+ it.throwable?.also { e ->
+ content += "${head}Dump stack trace for \"${e.current().name}\":\n"
+ content += e.toStackTrace()
+ }
+ }
+ return content
+ }
+
+ /**
+ * 格式化为本地时间格式
+ * @return [String]
+ */
+ private fun Long.format() = SimpleDateFormat.getDateTimeInstance().format(Date(this))
+
+ /**
+ * 格式化消息字符串样式
+ * @return [String]
+ */
+ private fun String.format() = replace(oldValue = "--", newValue = "\n--")
+
+ /**
+ * 获取完整的异常堆栈内容
+ * @return [String]
+ */
+ private fun Throwable.toStackTrace() = ByteArrayOutputStream().also { printStackTrace(PrintStream(it)) }.toString().trim()
+
+ override fun onCreateContextMenu(menu: ContextMenu?, v: View?, menuInfo: ContextMenu.ContextMenuInfo?) {
+ menuInflater.inflate(R.menu.menu_logger_action, menu)
+ super.onCreateContextMenu(menu, v, menuInfo)
+ }
+
+ override fun onContextItemSelected(item: MenuItem): Boolean {
+ if (item.menuInfo is AdapterView.AdapterContextMenuInfo)
+ (item.menuInfo as? AdapterView.AdapterContextMenuInfo?)?.also {
+ if (item.itemId == R.id.logger_copy)
+ copyToClipboard(listData[it.position].let { e -> e.toString() + (e.throwable?.toStackTrace()?.let { t -> "\n$t" } ?: "") })
+ }
+ return super.onContextItemSelected(item)
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ if (requestCode == WRITE_REQUEST_CODE && resultCode == Activity.RESULT_OK) runCatching {
+ data?.data?.let {
+ contentResolver?.openOutputStream(it)?.apply { write(loggerContents.toByteArray()) }?.close()
+ toast(LocaleString.exportAllLogsSuccess)
+ } ?: toast(LocaleString.exportAllLogsFail)
+ }.onFailure { toast(LocaleString.exportAllLogsFail) }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ /** 执行更新 */
+ refreshData()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/fankes/apperrorstracking/ui/activity/main/MainActivity.kt b/app/src/main/java/com/fankes/apperrorstracking/ui/activity/main/MainActivity.kt
index 83ca7b0..027b924 100644
--- a/app/src/main/java/com/fankes/apperrorstracking/ui/activity/main/MainActivity.kt
+++ b/app/src/main/java/com/fankes/apperrorstracking/ui/activity/main/MainActivity.kt
@@ -32,6 +32,7 @@ import com.fankes.apperrorstracking.data.ConfigData.bind
import com.fankes.apperrorstracking.databinding.ActivityMainBinding
import com.fankes.apperrorstracking.locale.LocaleString
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
+import com.fankes.apperrorstracking.ui.activity.debug.LoggerActivity
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsMutedActivity
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsRecordActivity
import com.fankes.apperrorstracking.utils.factory.*
@@ -78,6 +79,8 @@ class MainActivity : BaseActivity() {
/** 功能管理按钮点击事件 */
binding.viewErrorsRecordButton.setOnClickListener { whenActivated { navigate() } }
binding.viewMutedErrorsAppsButton.setOnClickListener { whenActivated { navigate() } }
+ /** 调试日志按钮点击事件 */
+ binding.titleLoggerIcon.setOnClickListener { navigate() }
/** 重启按钮点击事件 */
binding.titleRestartIcon.setOnClickListener { FrameworkTool.restartSystem(context = this) }
/** 项目地址按钮点击事件 */
diff --git a/app/src/main/java/com/fankes/apperrorstracking/utils/tool/FrameworkTool.kt b/app/src/main/java/com/fankes/apperrorstracking/utils/tool/FrameworkTool.kt
index b2fdff5..ed6164a 100644
--- a/app/src/main/java/com/fankes/apperrorstracking/utils/tool/FrameworkTool.kt
+++ b/app/src/main/java/com/fankes/apperrorstracking/utils/tool/FrameworkTool.kt
@@ -43,7 +43,7 @@ import com.highcapable.yukihookapi.hook.xposed.channel.data.ChannelData
object FrameworkTool {
/** 系统框架包名 */
- private const val SYSTEM_FRAMEWORK_NAME = "android"
+ const val SYSTEM_FRAMEWORK_NAME = "android"
private const val CALL_APP_ERRORS_DATA_GET = "call_app_errors_data_get"
private const val CALL_MUTED_ERRORS_APP_DATA_GET = "call_muted_app_errors_data_get"
diff --git a/app/src/main/res/drawable/bg_logger_d_round.xml b/app/src/main/res/drawable/bg_logger_d_round.xml
new file mode 100644
index 0000000..66ed1ed
--- /dev/null
+++ b/app/src/main/res/drawable/bg_logger_d_round.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bg_logger_e_round.xml b/app/src/main/res/drawable/bg_logger_e_round.xml
new file mode 100644
index 0000000..870483c
--- /dev/null
+++ b/app/src/main/res/drawable/bg_logger_e_round.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bg_logger_i_round.xml b/app/src/main/res/drawable/bg_logger_i_round.xml
new file mode 100644
index 0000000..486e5e8
--- /dev/null
+++ b/app/src/main/res/drawable/bg_logger_i_round.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bg_logger_w_round.xml b/app/src/main/res/drawable/bg_logger_w_round.xml
new file mode 100644
index 0000000..ca080a3
--- /dev/null
+++ b/app/src/main/res/drawable/bg_logger_w_round.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_refresh.xml b/app/src/main/res/drawable/ic_refresh.xml
new file mode 100644
index 0000000..3efa3f3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_refresh.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/layout/activitiy_logger.xml b/app/src/main/res/layout/activitiy_logger.xml
new file mode 100644
index 0000000..df9ad4e
--- /dev/null
+++ b/app/src/main/res/layout/activitiy_logger.xml
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 9224fde..95d8c9f 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -29,6 +29,18 @@
android:textSize="25sp"
android:textStyle="bold" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dia_logger_filter.xml b/app/src/main/res/layout/dia_logger_filter.xml
new file mode 100644
index 0000000..1f984d4
--- /dev/null
+++ b/app/src/main/res/layout/dia_logger_filter.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_logger_action.xml b/app/src/main/res/menu/menu_logger_action.xml
new file mode 100644
index 0000000..dcc368c
--- /dev/null
+++ b/app/src/main/res/menu/menu_logger_action.xml
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file