Added function AppErrorsRecordActivity and support native crash message

This commit is contained in:
2022-05-12 04:18:00 +08:00
parent d097054cb0
commit 443220a078
20 changed files with 571 additions and 23 deletions

View File

@@ -44,13 +44,15 @@ Added more features to app's crash dialog, fixed custom rom deleted dialog, the
- 打印异常堆栈到控制台功能
- 异常历史记录功能,可通过通知栏磁贴“异常历史记录”进入和模块主界面进入(正在开发)
- 支持 Android 10 及以上系统的深色模式
## Future
此项目依然在开发中,现在未解决的问题和包含的问题如下
- 异常历史记录和排除列表正在开发
- 排除列表和异常历史记录导出、清空功能正在开发
- 后台进程可能依然会弹出崩溃对话框且开发者选项里的设置无效,还在排查

View File

@@ -31,6 +31,28 @@
<activity
android:name=".ui.activity.AppErrorsDetailActivity"
android:exported="true"
android:screenOrientation="behind" />
android:launchMode="singleTask"
android:screenOrientation="behind"
android:taskAffinity=":detail" />
<activity
android:name=".ui.activity.AppErrorsRecordActivity"
android:exported="true"
android:label="@string/errors_record"
android:launchMode="singleTask"
android:screenOrientation="behind"
android:taskAffinity=":history" />
<service
android:name=".service.QuickStartTileService"
android:exported="true"
android:icon="@drawable/ic_debug"
android:label="@string/errors_record"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
</application>
</manifest>

View File

@@ -0,0 +1,48 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/5/12.
*/
package com.fankes.apperrorstracking.const
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
/**
* 全局常量
*/
object Const {
/** [AppErrorsInfoBean] 传值 */
const val EXTRA_APP_ERRORS_INFO = "app_errors_info_extra"
/** 模块接收广播 */
const val ACTION_MODULE_HANDLER_RECEIVER = "module_handler_action"
/** 宿主接收广播 */
const val ACTION_HOST_HANDLER_RECEIVER = "host_handler_action"
/** [AppErrorsInfoBean] 控制数据 */
const val TYPE_APP_ERRORS_DATA_CONTROL = "app_errors_data_control_type"
/** [AppErrorsInfoBean] 获取数据 */
const val TYPE_APP_ERRORS_DATA_CONTROL_GET_DATA = "app_errors_data_control_get_data_type"
/** [AppErrorsInfoBean] 获取到的数据内容 */
const val TAG_APP_ERRORS_DATA_CONTENT = "app_errors_data_content_tag"
}

View File

@@ -41,6 +41,7 @@ import android.widget.TextView
import androidx.core.content.res.ResourcesCompat
import com.fankes.apperrorstracking.R
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
import com.fankes.apperrorstracking.const.Const
import com.fankes.apperrorstracking.locale.LocaleString
import com.fankes.apperrorstracking.ui.activity.AppErrorsDetailActivity
import com.fankes.apperrorstracking.utils.drawable.drawabletoolbox.DrawableBuilder
@@ -51,12 +52,11 @@ import com.highcapable.yukihookapi.hook.factory.field
import com.highcapable.yukihookapi.hook.factory.hasMethod
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.log.loggerE
import com.highcapable.yukihookapi.hook.type.android.ActivityThreadClass
import com.highcapable.yukihookapi.hook.type.android.MessageClass
object FrameworkHooker : YukiBaseHooker() {
const val APP_ERRORS_INFO = "app_errors_info"
private const val AppErrorsClass = "com.android.server.am.AppErrors"
private const val AppErrorResultClass = "com.android.server.am.AppErrorResult"
@@ -110,6 +110,23 @@ object FrameworkHooker : YukiBaseHooker() {
}
}
/** 宿主广播接收器 */
private val hostHandlerReceiver by lazy {
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent == null) return
when (intent.getStringExtra(Const.TYPE_APP_ERRORS_DATA_CONTROL)) {
Const.TYPE_APP_ERRORS_DATA_CONTROL_GET_DATA ->
context?.sendBroadcast(Intent().apply {
action = Const.ACTION_MODULE_HANDLER_RECEIVER
putExtra(Const.TYPE_APP_ERRORS_DATA_CONTROL, Const.TYPE_APP_ERRORS_DATA_CONTROL_GET_DATA)
putExtra(Const.TAG_APP_ERRORS_DATA_CONTENT, appErrorsRecords)
})
}
}
}
}
/**
* 注册广播接收器
* @param context 实例
@@ -118,6 +135,7 @@ object FrameworkHooker : YukiBaseHooker() {
if (isRegisterReceiver) return
context.registerReceiver(userPresentReceiver, IntentFilter().apply { addAction(Intent.ACTION_USER_PRESENT) })
context.registerReceiver(localeChangedReceiver, IntentFilter().apply { addAction(Intent.ACTION_LOCALE_CHANGED) })
context.registerReceiver(hostHandlerReceiver, IntentFilter().apply { addAction(Const.ACTION_HOST_HANDLER_RECEIVER) })
isRegisterReceiver = true
}
@@ -161,6 +179,16 @@ object FrameworkHooker : YukiBaseHooker() {
}
override fun onHook() {
/** 注入全局监听 */
ActivityThreadClass.hook {
injectMember {
method {
name = "currentApplication"
emptyParam()
}
afterHook { result<Context>()?.let { registerReceiver(it) } }
}
}
/** 干掉原生错误对话框 - 如果有 */
ErrorDialogControllerClass.hook {
injectMember {
@@ -229,8 +257,6 @@ object FrameworkHooker : YukiBaseHooker() {
/** 是否短时内重复错误 */
val isRepeating = AppErrorDialog_DataClass.clazz.field { name = "repeating" }.get(errData).boolean()
/** 注册广播 */
registerReceiver(context)
/** 打印错误日志 */
loggerE(msg = "Process \"$packageName\" has crashed${if (isRepeating) " again" else ""}")
/** 判断是否被忽略 - 在后台就不显示对话框 */
@@ -317,20 +343,26 @@ object FrameworkHooker : YukiBaseHooker() {
/** 当前异常信息 */
args().last().cast<ApplicationErrorReport.CrashInfo>()?.also { crashInfo ->
/** 添加到第一位 */
appErrorsRecords.add(
0, AppErrorsInfoBean(
packageName = appInfo?.packageName ?: "",
isNativeCrash = crashInfo.exceptionClassName.lowercase() == "native crash",
exceptionClassName = crashInfo.exceptionClassName ?: "",
exceptionMessage = crashInfo.exceptionMessage ?: "",
throwFileName = crashInfo.throwFileName ?: "",
throwClassName = crashInfo.throwClassName ?: "",
throwMethodName = crashInfo.throwMethodName ?: "",
throwLineNumber = crashInfo.throwLineNumber,
stackTrace = crashInfo.stackTrace?.trim() ?: "",
timestamp = System.currentTimeMillis()
(crashInfo.exceptionClassName.lowercase() == "native crash").also { isNativeCrash ->
appErrorsRecords.add(
0, AppErrorsInfoBean(
packageName = appInfo?.packageName ?: "",
isNativeCrash = isNativeCrash,
exceptionClassName = crashInfo.exceptionClassName ?: "",
exceptionMessage = if (isNativeCrash) crashInfo.stackTrace.let {
if (it.contains(other = "Abort message: '"))
runCatching { it.split("Abort message: '")[1].split("'")[0] }.getOrNull()
?: crashInfo.exceptionMessage ?: "" else crashInfo.exceptionMessage ?: ""
} else crashInfo.exceptionMessage ?: "",
throwFileName = crashInfo.throwFileName ?: "",
throwClassName = crashInfo.throwClassName ?: "",
throwMethodName = crashInfo.throwMethodName ?: "",
throwLineNumber = crashInfo.throwLineNumber,
stackTrace = crashInfo.stackTrace?.trim() ?: "",
timestamp = System.currentTimeMillis()
)
)
)
}
}
}
}

View File

@@ -0,0 +1,35 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/5/11.
*/
package com.fankes.apperrorstracking.service
import android.service.quicksettings.TileService
import com.fankes.apperrorstracking.ui.activity.AppErrorsRecordActivity
import com.fankes.apperrorstracking.utils.factory.navigate
class QuickStartTileService : TileService() {
override fun onClick() {
super.onClick()
/** 启动异常历史记录窗口 */
navigate<AppErrorsRecordActivity>()
}
}

View File

@@ -29,8 +29,8 @@ import android.content.Intent
import androidx.core.view.isGone
import com.fankes.apperrorstracking.R
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
import com.fankes.apperrorstracking.const.Const
import com.fankes.apperrorstracking.databinding.ActivityAppErrorsDetailBinding
import com.fankes.apperrorstracking.hook.entity.FrameworkHooker
import com.fankes.apperrorstracking.locale.LocaleString
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
import com.fankes.apperrorstracking.utils.factory.*
@@ -52,7 +52,7 @@ class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
* @param isOutSide 是否从外部启动
*/
fun start(context: Context, appErrorsInfo: AppErrorsInfoBean, isOutSide: Boolean = false) =
context.navigate<AppErrorsDetailActivity>(isOutSide) { putExtra(FrameworkHooker.APP_ERRORS_INFO, appErrorsInfo) }
context.navigate<AppErrorsDetailActivity>(isOutSide) { putExtra(Const.EXTRA_APP_ERRORS_INFO, appErrorsInfo) }
}
/** 预导出的异常堆栈 */
@@ -60,7 +60,7 @@ class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
override fun onCreate() {
val appErrorsInfo =
intent?.getSerializableExtra(FrameworkHooker.APP_ERRORS_INFO) as? AppErrorsInfoBean ?: return toastAndFinish()
intent?.getSerializableExtra(Const.EXTRA_APP_ERRORS_INFO) as? AppErrorsInfoBean ?: return toastAndFinish()
/** 创建异常堆栈模板 */
fun createStack() =
@@ -113,7 +113,7 @@ class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
}
override fun onBackPressed() {
intent?.removeExtra(FrameworkHooker.APP_ERRORS_INFO)
intent?.removeExtra(Const.EXTRA_APP_ERRORS_INFO)
finish()
}
}

View File

@@ -0,0 +1,142 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/5/11.
*/
@file:Suppress("UNCHECKED_CAST")
package com.fankes.apperrorstracking.ui.activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import androidx.core.view.isVisible
import com.fankes.apperrorstracking.R
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
import com.fankes.apperrorstracking.const.Const
import com.fankes.apperrorstracking.databinding.ActivityAppErrorsRecordBinding
import com.fankes.apperrorstracking.databinding.AdapterAppErrorsRecordBinding
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
import com.fankes.apperrorstracking.utils.factory.appIcon
import com.fankes.apperrorstracking.utils.factory.appName
import com.fankes.apperrorstracking.utils.factory.toast
import java.text.SimpleDateFormat
import java.util.*
class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
/** 回调适配器改变 */
private var onChanged: (() -> Unit)? = null
/** 全部的 APP 异常数据 */
private val listData = ArrayList<AppErrorsInfoBean>()
override fun onCreate() {
binding.titleBackIcon.setOnClickListener { onBackPressed() }
binding.clearAllIcon.setOnClickListener {
// TODO 待实现
toast(msg = "Coming soon")
}
binding.exportAllIcon.setOnClickListener {
// TODO 待实现
toast(msg = "Coming soon")
}
/** 设置列表元素和 Adapter */
binding.listView.apply {
adapter = object : BaseAdapter() {
override fun getCount() = listData.size
override fun getItem(position: Int) = listData[position]
override fun getItemId(position: Int) = position.toLong()
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
var cView = convertView
val holder: AdapterAppErrorsRecordBinding
if (convertView == null) {
holder = AdapterAppErrorsRecordBinding.inflate(LayoutInflater.from(context))
cView = holder.root
cView.tag = holder
} else holder = convertView.tag as AdapterAppErrorsRecordBinding
getItem(position).also {
holder.appIcon.setImageDrawable(appIcon(it.packageName))
holder.appNameText.text = appName(it.packageName)
holder.errorsTimeText.text = SimpleDateFormat.getDateTimeInstance().format(Date(it.timestamp))
holder.errorTypeIcon.setImageResource(if (it.isNativeCrash) R.drawable.ic_cpp else R.drawable.ic_java)
holder.errorTypeText.text = if (it.isNativeCrash) "Native crash" else it.exceptionClassName.let { text ->
if (text.contains(other = ".")) text.split(".").let { e -> e[e.lastIndex] } else text
}
holder.errorMsgText.text = it.exceptionMessage
}
return cView!!
}
}.apply {
setOnItemClickListener { _, _, p, _ -> AppErrorsDetailActivity.start(context, listData[p]) }
onChanged = { notifyDataSetChanged() }
}
}
/** 注册广播 */
registerReceiver(moduleHandlerReceiver, IntentFilter().apply { addAction(Const.ACTION_MODULE_HANDLER_RECEIVER) })
}
/** 更新列表数据 */
private fun refreshData() {
sendBroadcast(Intent().apply {
action = Const.ACTION_HOST_HANDLER_RECEIVER
putExtra(Const.TYPE_APP_ERRORS_DATA_CONTROL, Const.TYPE_APP_ERRORS_DATA_CONTROL_GET_DATA)
})
}
/** 模块广播接收器 */
private val moduleHandlerReceiver by lazy {
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent == null) return
when (intent.getStringExtra(Const.TYPE_APP_ERRORS_DATA_CONTROL)) {
Const.TYPE_APP_ERRORS_DATA_CONTROL_GET_DATA ->
(intent.getSerializableExtra(Const.TAG_APP_ERRORS_DATA_CONTENT) as? ArrayList<AppErrorsInfoBean>)?.also {
listData.clear()
it.takeIf { e -> e.isNotEmpty() }?.forEach { e -> listData.add(e) }
onChanged?.invoke()
binding.listView.isVisible = listData.isNotEmpty()
binding.listNoDataView.isVisible = listData.isEmpty()
}
}
}
}
}
override fun onResume() {
super.onResume()
/** 执行更新 */
refreshData()
}
override fun onDestroy() {
super.onDestroy()
/** 取消注册 */
unregisterReceiver(moduleHandlerReceiver)
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#777777">
<item>
<shape android:shape="rectangle">
<solid android:color="#666E6E6E" />
<corners android:radius="15dp" />
</shape>
</item>
</ripple>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#777777">
<item>
<shape android:shape="rectangle">
<solid android:color="#66E4E4E4" />
<corners android:radius="15dp" />
</shape>
</item>
</ripple>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#EF5350" />
<corners android:radius="5dp" />
</shape>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="150dp"
android:height="150dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M785.8,927.4h-64.9c-9.7,0 -16.9,-9.7 -14.3,-19.5 0,-0.6 0,-0.6 0.6,-1.3 4.5,-16.9 -18.1,-26.6 -27.2,-11.7 -7.2,11 -13,20.2 -16.9,26a14.8,14.8 0,0 1,-12.3 6.5h-56.5c-9.7,0 -16.9,-9.7 -14.3,-19.5 0,-0.6 0,-0.6 0.6,-1.3 4.5,-16.9 -18.2,-26.6 -27.3,-11.7 -7.1,11 -13,20.2 -16.9,26a14.8,14.8 0,0 1,-12.3 6.5h-55.8c-9.7,0 -16.9,-9.7 -14.3,-19.5 0,-0.6 0,-0.6 0.6,-1.3 4.5,-16.9 -18.2,-26.6 -27.3,-11.7 -7.1,11 -13,20.2 -16.9,26a14.8,14.8 0,0 1,-12.3 6.5H360c-9.7,0 -16.9,-9.7 -14.3,-19.5 0,-0.6 0,-0.6 0.6,-1.3 4.5,-16.9 -18.2,-26.6 -27.3,-11.7 -7.1,11 -13,20.2 -16.9,26a14.8,14.8 0,0 1,-12.4 6.5H207.5a13.1,13.1 0,0 1,-7.1 -1.9c-43.5,-25.3 -62.9,-84.4 0.6,-138.9 42.2,-36.4 46.7,-94.8 45.4,-147.3 0,-8.4 6.5,-15.6 14.9,-15.6h526.4c7.1,0 13,4.5 14.3,11.7 14.9,64.9 20.1,116.2 32.4,205.8 8.4,65.5 -48.7,86.3 -48.7,86.3zM215.3,570.4c-20.1,0 -29.2,-14.9 -20.8,-32.5l70.8,-150.6c8.4,-18.2 31.8,-32.5 51.3,-32.5h113.6a14.7,14.7 0,0 0,14.9 -14.9V147.8c0.6,-28.6 23.4,-51.3 51.3,-51.3h42.2c27.9,0 51.3,22.7 51.3,51.3v190.8c0,8.4 6.5,14.9 14.9,14.9h109.7c20.1,0 42.8,14.9 51.3,32.4l70.7,150.6c8.5,18.2 -0.6,32.4 -20.7,32.4l-600.4,1.3z"
android:fillColor="#ffffff"/>
</vector>

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="250dp"
android:height="250dp"
android:viewportWidth="1024"
android:viewportHeight="1024"
tools:ignore="VectorRaster">
<path
android:fillColor="#ffffff"
android:pathData="M510.9,1024c123.9,0 231.8,-67.1 289.8,-168.2h194.3v-111.4L841.6,744.4c2.3,-18.2 4.5,-36.4 4.5,-55.7v-55.7h148.9v-111.4h-147.7L847.3,466c0,-19.3 -2.3,-37.5 -4.5,-55.7h154.6v-111.4L801.8,298.9c-25,-43.2 -60.2,-80.7 -102.3,-109.1l113.7,-109.1L734.8,0 590.4,138.7c-26.1,-5.7 -52.3,-9.1 -79.6,-9.1 -27.3,0 -53.4,3.4 -78.4,9.1L291.5,0l-78.4,78.4 110.2,109.1c-40.9,28.4 -76.1,65.9 -101.1,109.1L26.7,296.6L26.7,409.1h154.6c-2.3,18.2 -4.5,37.5 -4.5,55.7v55.7L26.7,520.5v111.4h148.9v55.7c0,19.3 2.3,37.5 4.5,55.7L26.7,743.3v111.4h194.3c58,101.1 165.9,169.3 289.8,169.3zM399.5,409.1h223.9v111.4L399.5,520.5L399.5,409.1zM399.5,631.9h223.9v111.4L399.5,743.3v-111.4z" />
</vector>

View File

@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorThemeBackground"
android:orientation="vertical"
tools:context=".ui.activity.AppErrorsRecordActivity"
tools:ignore="ContentDescription,UseCompoundDrawables">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="0dp"
android:gravity="center|start"
android:paddingLeft="15dp"
android:paddingTop="15dp"
android:paddingRight="15dp"
android:paddingBottom="15dp">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/title_back_icon"
style="?android:attr/selectableItemBackgroundBorderless"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="20dp"
android:src="@mipmap/ic_back"
android:tint="@color/colorTextGray"
android:tooltipText="@string/back" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="2.5dp"
android:layout_weight="1"
android:singleLine="true"
android:text="@string/errors_record"
android:textColor="@color/colorTextGray"
android:textSize="19sp"
android:textStyle="bold" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/clear_all_icon"
android:layout_width="26dp"
android:layout_height="26dp"
android:layout_marginEnd="15dp"
android:src="@drawable/ic_clear"
android:tint="@color/colorTextGray"
android:tooltipText="@string/clear_all" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/export_all_icon"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginEnd="10dp"
android:src="@drawable/ic_export"
android:tint="@color/colorTextGray"
android:tooltipText="@string/export_all" />
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="5dp">
<TextView
android:id="@+id/list_no_data_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:lineSpacingExtra="6dp"
android:text="@string/no_errors_data"
android:textColor="@color/colorTextDark"
android:textSize="17sp" />
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@color/trans"
android:dividerHeight="15dp"
android:fadingEdgeLength="10dp"
android:listSelector="@color/trans"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:paddingBottom="15dp"
android:requiresFadingEdge="vertical"
android:scrollbars="none"
android:visibility="gone" />
</FrameLayout>
</LinearLayout>

View File

@@ -0,0 +1,104 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_permotion_ripple"
android:gravity="center|start"
android:orientation="horizontal"
android:padding="10dp"
tools:ignore="ContentDescription,UseCompoundDrawables,SmallSp">
<androidx.cardview.widget.CardView
android:layout_width="45dp"
android:layout_height="45dp"
app:cardBackgroundColor="@color/trans"
app:cardCornerRadius="10dp"
app:cardElevation="0dp">
<ImageView
android:id="@+id/app_icon"
android:layout_width="45dp"
android:layout_height="45dp" />
</androidx.cardview.widget.CardView>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:layout_weight="1"
android:gravity="center|start"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:gravity="center|start"
android:orientation="horizontal">
<TextView
android:id="@+id/app_name_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_weight="1"
android:ellipsize="end"
android:singleLine="true"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<TextView
android:id="@+id/errors_time_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_weight="1"
android:alpha="0.85"
android:ellipsize="start"
android:gravity="end"
android:singleLine="true"
android:textColor="@color/colorTextDark"
android:textSize="11sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center|start"
android:orientation="horizontal">
<TextView
android:id="@+id/error_type_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:background="@drawable/bg_red_round"
android:ellipsize="end"
android:paddingLeft="3dp"
android:paddingTop="0.5dp"
android:paddingRight="3dp"
android:paddingBottom="0.5dp"
android:singleLine="true"
android:textColor="@color/white"
android:textSize="9sp" />
<TextView
android:id="@+id/error_msg_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:singleLine="true"
android:textColor="@color/colorTextDark"
android:textSize="11sp" />
</LinearLayout>
</LinearLayout>
<ImageView
android:id="@+id/error_type_icon"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginEnd="10dp" />
</LinearLayout>

View File

@@ -28,4 +28,8 @@
<string name="print_to_logcat_success">コンソールに印刷されていました</string>
<string name="output_stack_success">エクスポートされたエラースタックされていました</string>
<string name="output_stack_fail">エラースタックのエクスポートに失敗しました</string>
<string name="export_all">すべてエクスポート</string>
<string name="clear_all">すべてクリア</string>
<string name="errors_record">エラー履歴記錄</string>
<string name="no_errors_data">一時的にエラーなデータレコードはありません</string>
</resources>

View File

@@ -28,4 +28,8 @@
<string name="print_to_logcat_success">已打印到控制台</string>
<string name="output_stack_success">已导出异常堆栈</string>
<string name="output_stack_fail">导出异常堆栈失败</string>
<string name="export_all">导出全部</string>
<string name="clear_all">清空全部</string>
<string name="errors_record">异常历史记录</string>
<string name="no_errors_data">暂时没有异常数据记录</string>
</resources>

View File

@@ -28,4 +28,8 @@
<string name="print_to_logcat_success">已打印到控制台</string>
<string name="output_stack_success">已導出異常堆棧</string>
<string name="output_stack_fail">導出異常堆棧失敗</string>
<string name="export_all">導出全部</string>
<string name="clear_all">清空全部</string>
<string name="errors_record">異常歷史記錄</string>
<string name="no_errors_data">暫時沒有異常數據紀錄</string>
</resources>

View File

@@ -28,4 +28,8 @@
<string name="print_to_logcat_success">已打印到控制台</string>
<string name="output_stack_success">已導出異常堆棧</string>
<string name="output_stack_fail">導出異常堆棧失敗</string>
<string name="export_all">導出全部</string>
<string name="clear_all">清空全部</string>
<string name="errors_record">異常歷史記錄</string>
<string name="no_errors_data">暫時沒有異常數據紀錄</string>
</resources>

View File

@@ -28,4 +28,8 @@
<string name="print_to_logcat_success">已打印到控制台</string>
<string name="output_stack_success">已導出異常堆棧</string>
<string name="output_stack_fail">導出異常堆棧失敗</string>
<string name="export_all">導出全部</string>
<string name="clear_all">清空全部</string>
<string name="errors_record">異常歷史記錄</string>
<string name="no_errors_data">暫時沒有異常數據紀錄</string>
</resources>

View File

@@ -27,4 +27,8 @@
<string name="print_to_logcat_success">Printed to logcat</string>
<string name="output_stack_success">Export exception stack succeeded</string>
<string name="output_stack_fail">Failed to export exception stack</string>
<string name="export_all">Export all</string>
<string name="clear_all">Clear all</string>
<string name="errors_record">AppErrorsRecord</string>
<string name="no_errors_data">No errors data for now</string>
</resources>