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