mirror of
https://github.com/KitsunePie/AppErrorsTracking.git
synced 2025-09-01 16:55:18 +08:00
Added new style in app errors dialog and merge code from FrameworkHooker
This commit is contained in:
@@ -54,6 +54,16 @@
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity
|
||||
android:name=".ui.activity.errors.AppErrorsDisplayActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:label="@string/empty_lable"
|
||||
android:launchMode="singleTask"
|
||||
android:screenOrientation="behind"
|
||||
android:taskAffinity=":display"
|
||||
android:theme="@style/Theme.AppErrorsTracking.Translucent" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.activity.errors.AppErrorsDetailActivity"
|
||||
android:exported="true"
|
||||
|
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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/10.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.bean
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* 应用异常信息显示 bean
|
||||
* @param packageName APP 包名
|
||||
* @param appName APP 名称
|
||||
* @param title 标题
|
||||
* @param isApp 是否为 APP
|
||||
* @param isShowReopenButton 是否显示重新打开按钮
|
||||
*/
|
||||
data class AppErrorsDisplayBean(
|
||||
var packageName: String,
|
||||
var appName: String,
|
||||
var title: String,
|
||||
var isApp: Boolean,
|
||||
var isShowReopenButton: Boolean
|
||||
) : Serializable
|
@@ -27,22 +27,14 @@ import android.app.ApplicationErrorReport
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.graphics.Color
|
||||
import android.os.Message
|
||||
import android.text.TextUtils
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.fankes.apperrorstracking.bean.AppErrorsDisplayBean
|
||||
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsDetailActivity
|
||||
import com.fankes.apperrorstracking.utils.drawable.drawabletoolbox.DrawableBuilder
|
||||
import com.fankes.apperrorstracking.utils.factory.*
|
||||
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsDisplayActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.appName
|
||||
import com.fankes.apperrorstracking.utils.factory.isAppCanOpened
|
||||
import com.fankes.apperrorstracking.utils.factory.openApp
|
||||
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
|
||||
import com.highcapable.yukihookapi.hook.bean.VariousClass
|
||||
import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
|
||||
@@ -72,9 +64,6 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
"com.android.server.am.ErrorDialogController"
|
||||
)
|
||||
|
||||
/** 已打开的错误对话框数组 */
|
||||
private var openedErrorsDialogs = hashMapOf<String, DialogBuilder>()
|
||||
|
||||
/** 已忽略错误的 APP 数组 - 直到重新解锁 */
|
||||
private var ignoredErrorsIfUnlockApps = hashSetOf<String>()
|
||||
|
||||
@@ -84,45 +73,6 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
/** 已记录的 APP 异常信息数组 - 直到重新启动 */
|
||||
private val appErrorsRecords = arrayListOf<AppErrorsInfoBean>()
|
||||
|
||||
/**
|
||||
* 获取最新的 APP 错误信息
|
||||
* @param packageName 包名
|
||||
* @return [AppErrorsInfoBean] or null
|
||||
*/
|
||||
private fun lastAppErrorsInfo(packageName: String) =
|
||||
appErrorsRecords.takeIf { it.isNotEmpty() }?.filter { it.packageName == packageName }?.get(0)
|
||||
|
||||
/**
|
||||
* 创建对话框按钮
|
||||
* @param context 实例
|
||||
* @param drawableId 按钮图标
|
||||
* @param content 按钮文本
|
||||
* @param it 点击事件回调
|
||||
* @return [LinearLayout]
|
||||
*/
|
||||
private fun createButtonItem(context: Context, drawableId: Int, content: String, it: () -> Unit) =
|
||||
LinearLayout(context).apply {
|
||||
background = DrawableBuilder().rounded().cornerRadius(15.dp(context)).ripple().rippleColor(0xFFAAAAAA.toInt()).build()
|
||||
gravity = Gravity.CENTER or Gravity.START
|
||||
layoutParams =
|
||||
ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
addView(ImageView(context).apply {
|
||||
setImageDrawable(ResourcesCompat.getDrawable(moduleAppResources, drawableId, null))
|
||||
layoutParams = ViewGroup.LayoutParams(25.dp(context), 25.dp(context))
|
||||
setColorFilter(if (context.isSystemInDarkMode) Color.WHITE else Color.BLACK)
|
||||
})
|
||||
addView(View(context).apply { layoutParams = ViewGroup.LayoutParams(15.dp(context), 0) })
|
||||
addView(TextView(context).apply {
|
||||
text = content
|
||||
textSize = 16f
|
||||
ellipsize = TextUtils.TruncateAt.END
|
||||
setSingleLine()
|
||||
setTextColor(if (context.isSystemInDarkMode) 0xFFDDDDDD.toInt() else 0xFF777777.toInt())
|
||||
})
|
||||
setPadding(19.dp(context), 16.dp(context), 19.dp(context), 16.dp(context))
|
||||
setOnClickListener { it() }
|
||||
}
|
||||
|
||||
/** 注册 */
|
||||
private fun register() {
|
||||
onAppLifecycle {
|
||||
@@ -132,9 +82,12 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
registerReceiver(Intent.ACTION_LOCALE_CHANGED) { _, _ -> refreshModuleAppResources() }
|
||||
}
|
||||
FrameworkTool.Host.with(instance = this) {
|
||||
onOpenAppUsedFramework { appContext.openApp(it) }
|
||||
onPushAppErrorsInfoData { appErrorsRecords }
|
||||
onRemoveAppErrorsInfoData { appErrorsRecords.remove(it) }
|
||||
onClearAppErrorsInfoData { appErrorsRecords.clear() }
|
||||
onIgnoredErrorsIfUnlock { ignoredErrorsIfUnlockApps.add(it) }
|
||||
onIgnoredErrorsIfRestart { ignoredErrorsIfRestartApps.add(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,74 +167,16 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
/** 判断是否被忽略 - 在后台就不显示对话框 */
|
||||
if (ignoredErrorsIfUnlockApps.contains(packageName) || ignoredErrorsIfRestartApps.contains(packageName) || errResult == -2)
|
||||
return@afterHook
|
||||
/** 关闭重复的对话框 */
|
||||
openedErrorsDialogs[packageName]?.cancel()
|
||||
/** 创建自定义对话框 */
|
||||
context.showDialog {
|
||||
title = if (isRepeating) LocaleString.aerrRepeatedTitle(appName) else LocaleString.aerrTitle(appName)
|
||||
view = LinearLayout(context).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
/** 应用信息按钮 */
|
||||
val appInfoButton =
|
||||
createButtonItem(context, R.drawable.ic_baseline_info, LocaleString.appInfo) {
|
||||
cancel()
|
||||
context.openSelfSetting(packageName)
|
||||
}
|
||||
|
||||
/** 关闭应用按钮 */
|
||||
val closeAppButton =
|
||||
createButtonItem(context, R.drawable.ic_baseline_close, LocaleString.closeApp) { cancel() }
|
||||
|
||||
/** 重新打开按钮 */
|
||||
val reOpenButton =
|
||||
createButtonItem(context, R.drawable.ic_baseline_refresh, LocaleString.reopenApp) {
|
||||
cancel()
|
||||
context.openApp(packageName)
|
||||
}
|
||||
|
||||
/** 错误详情按钮 */
|
||||
val errorDetailButton =
|
||||
createButtonItem(context, R.drawable.ic_baseline_bug_report, LocaleString.errorDetail) {
|
||||
cancel()
|
||||
lastAppErrorsInfo(packageName)?.let { AppErrorsDetailActivity.start(context, it, isOutSide = true) }
|
||||
?: context.toast(msg = "Invalid AppErrorsInfo")
|
||||
}
|
||||
|
||||
/** 忽略按钮 - 直到解锁 */
|
||||
val ignoredUntilUnlockButton =
|
||||
createButtonItem(context, R.drawable.ic_baseline_eject, LocaleString.ignoreIfUnlock) {
|
||||
cancel()
|
||||
ignoredErrorsIfUnlockApps.add(packageName)
|
||||
context.toast(LocaleString.ignoreIfUnlockTip(appName))
|
||||
}
|
||||
|
||||
/** 忽略按钮 - 直到重启 */
|
||||
val ignoredUntilRestartButton =
|
||||
createButtonItem(context, R.drawable.ic_baseline_eject, LocaleString.ignoreIfRestart) {
|
||||
cancel()
|
||||
ignoredErrorsIfRestartApps.add(packageName)
|
||||
context.toast(LocaleString.ignoreIfRestartTip(appName))
|
||||
}
|
||||
/** 判断进程是否为 APP */
|
||||
if (isApp) {
|
||||
addView(appInfoButton)
|
||||
addView(if (isRepeating.not() && context.isAppCanOpened(packageName)) reOpenButton else closeAppButton)
|
||||
} else addView(closeAppButton)
|
||||
/** 始终添加错误详情按钮 */
|
||||
addView(errorDetailButton)
|
||||
/** 始终添加忽略按钮 */
|
||||
addView(ignoredUntilUnlockButton)
|
||||
addView(ignoredUntilRestartButton)
|
||||
/** 设置边距 */
|
||||
setPadding(6.dp(context), 15.dp(context), 6.dp(context), 6.dp(context))
|
||||
}
|
||||
/** 设置取消对话框监听 */
|
||||
onCancel { openedErrorsDialogs.remove(packageName) }
|
||||
/** 记录实例 */
|
||||
openedErrorsDialogs[packageName] = this
|
||||
/** 只有 SystemUid 才能响应系统级别的对话框 */
|
||||
makeSystemAlert()
|
||||
}
|
||||
/** 启动错误对话框显示窗口 */
|
||||
AppErrorsDisplayActivity.start(
|
||||
context, AppErrorsDisplayBean(
|
||||
packageName = packageName,
|
||||
appName = appName,
|
||||
title = if (isRepeating) LocaleString.aerrRepeatedTitle(appName) else LocaleString.aerrTitle(appName),
|
||||
isApp = isApp,
|
||||
isShowReopenButton = isRepeating.not() && context.isAppCanOpened(packageName)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
injectMember {
|
||||
|
@@ -31,6 +31,7 @@ import androidx.core.view.ViewCompat
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.fankes.apperrorstracking.utils.factory.isNotSystemInDarkMode
|
||||
import com.fankes.apperrorstracking.utils.factory.toast
|
||||
import com.highcapable.yukihookapi.hook.factory.method
|
||||
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
|
||||
import java.lang.reflect.ParameterizedType
|
||||
@@ -69,4 +70,13 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
|
||||
/** 回调 [onCreate] 方法 */
|
||||
abstract fun onCreate()
|
||||
|
||||
/**
|
||||
* 弹出提示并退出
|
||||
* @param name 名称
|
||||
*/
|
||||
fun toastAndFinish(name: String) {
|
||||
toast(msg = "Invalid $name, exit")
|
||||
finish()
|
||||
}
|
||||
}
|
@@ -54,10 +54,9 @@ class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
|
||||
* 启动 [AppErrorsDetailActivity]
|
||||
* @param context 实例
|
||||
* @param appErrorsInfo 应用异常信息
|
||||
* @param isOutSide 是否从外部启动
|
||||
*/
|
||||
fun start(context: Context, appErrorsInfo: AppErrorsInfoBean, isOutSide: Boolean = false) =
|
||||
context.navigate<AppErrorsDetailActivity>(isOutSide) { putExtra(EXTRA_APP_ERRORS_INFO, appErrorsInfo) }
|
||||
fun start(context: Context, appErrorsInfo: AppErrorsInfoBean) =
|
||||
context.navigate<AppErrorsDetailActivity> { putExtra(EXTRA_APP_ERRORS_INFO, appErrorsInfo) }
|
||||
}
|
||||
|
||||
/** 预导出的异常堆栈 */
|
||||
@@ -89,7 +88,7 @@ class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
|
||||
|
||||
override fun onCreate() {
|
||||
val appErrorsInfo = runCatching { intent?.getSerializableExtra(EXTRA_APP_ERRORS_INFO) as? AppErrorsInfoBean }.getOrNull()
|
||||
?: return toastAndFinish()
|
||||
?: return toastAndFinish(name = "AppErrorsInfo")
|
||||
binding.appInfoItem.setOnClickListener { openSelfSetting(appErrorsInfo.packageName) }
|
||||
binding.titleBackIcon.setOnClickListener { onBackPressed() }
|
||||
binding.printIcon.setOnClickListener {
|
||||
@@ -129,12 +128,6 @@ class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
|
||||
contentResolver?.registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, observer)
|
||||
}
|
||||
|
||||
/** 弹出提示并退出 */
|
||||
private fun toastAndFinish() {
|
||||
toast(msg = "Invalid AppErrorsInfo, exit")
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (requestCode == WRITE_REQUEST_CODE && resultCode == Activity.RESULT_OK) runCatching {
|
||||
|
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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/6/1.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.ui.activity.errors
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.view.isVisible
|
||||
import com.fankes.apperrorstracking.bean.AppErrorsDisplayBean
|
||||
import com.fankes.apperrorstracking.databinding.ActivityAppErrorsDisplayBinding
|
||||
import com.fankes.apperrorstracking.databinding.DiaAppErrorsDisplayBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.navigate
|
||||
import com.fankes.apperrorstracking.utils.factory.openSelfSetting
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
import com.fankes.apperrorstracking.utils.factory.toast
|
||||
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
|
||||
|
||||
class AppErrorsDisplayActivity : BaseActivity<ActivityAppErrorsDisplayBinding>() {
|
||||
|
||||
companion object {
|
||||
|
||||
/** 当前实例 - 单例运行 */
|
||||
private var instance: AppErrorsDisplayActivity? = null
|
||||
|
||||
/** [AppErrorsDisplayBean] 传值 */
|
||||
private const val EXTRA_APP_ERRORS_DISPLAY = "app_errors_display_extra"
|
||||
|
||||
/**
|
||||
* 启动 [AppErrorsDisplayActivity]
|
||||
* @param context 实例
|
||||
* @param appErrorsDisplay 应用异常信息显示
|
||||
*/
|
||||
fun start(context: Context, appErrorsDisplay: AppErrorsDisplayBean) =
|
||||
context.navigate<AppErrorsDisplayActivity>(isOutSide = true) { putExtra(EXTRA_APP_ERRORS_DISPLAY, appErrorsDisplay) }
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
instance?.finish()
|
||||
instance = this
|
||||
val appErrorsDisplay = runCatching { intent?.getSerializableExtra(EXTRA_APP_ERRORS_DISPLAY) as? AppErrorsDisplayBean }.getOrNull()
|
||||
?: return toastAndFinish(name = "AppErrorsDisplay")
|
||||
/** 显示对话框 */
|
||||
showDialog {
|
||||
title = appErrorsDisplay.title
|
||||
bind<DiaAppErrorsDisplayBinding>().apply {
|
||||
appInfoItem.isVisible = appErrorsDisplay.isApp
|
||||
reopenAppItem.isVisible = appErrorsDisplay.isShowReopenButton
|
||||
closeAppItem.isVisible = appErrorsDisplay.isShowReopenButton.not()
|
||||
appInfoItem.setOnClickListener {
|
||||
cancel()
|
||||
openSelfSetting(appErrorsDisplay.packageName)
|
||||
}
|
||||
closeAppItem.setOnClickListener { cancel() }
|
||||
reopenAppItem.setOnClickListener {
|
||||
FrameworkTool.openAppUsedFramework(context, appErrorsDisplay.packageName)
|
||||
cancel()
|
||||
}
|
||||
errorDetailItem.setOnClickListener {
|
||||
FrameworkTool.fetchAppErrorsInfoData(context) { appErrorsInfos ->
|
||||
appErrorsInfos.takeIf { it.isNotEmpty() }
|
||||
?.filter { it.packageName == appErrorsDisplay.packageName }
|
||||
?.takeIf { it.isNotEmpty() }?.get(0)?.let {
|
||||
AppErrorsDetailActivity.start(context, it)
|
||||
cancel()
|
||||
} ?: toast(msg = "No errors founded")
|
||||
}
|
||||
}
|
||||
ignoreIfUnlockItem.setOnClickListener {
|
||||
FrameworkTool.ignoredErrorsIfUnlock(context, appErrorsDisplay.packageName) {
|
||||
toast(LocaleString.ignoreIfUnlockTip(appErrorsDisplay.appName))
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
ignoreIfRestartItem.setOnClickListener {
|
||||
FrameworkTool.ignoredErrorsIfRestart(context, appErrorsDisplay.packageName) {
|
||||
toast(LocaleString.ignoreIfRestartTip(appErrorsDisplay.appName))
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
onCancel { finish() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
instance = null
|
||||
}
|
||||
}
|
@@ -82,7 +82,7 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
|
||||
}
|
||||
}
|
||||
/** 设置列表元素和 Adapter */
|
||||
binding.listView.apply { // 改成 recycleview?
|
||||
binding.listView.apply {
|
||||
adapter = object : BaseAdapter() {
|
||||
|
||||
override fun getCount() = listData.size
|
||||
|
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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/6/1.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.ui.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.widget.LinearLayout
|
||||
import com.fankes.apperrorstracking.utils.drawable.drawabletoolbox.DrawableBuilder
|
||||
import com.fankes.apperrorstracking.utils.factory.dp
|
||||
|
||||
class ItemLinearLayout(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) {
|
||||
|
||||
init {
|
||||
gravity = Gravity.CENTER or Gravity.START
|
||||
background = DrawableBuilder()
|
||||
.rounded()
|
||||
.cornerRadius(15.dp(context))
|
||||
.ripple()
|
||||
.rippleColor(0xFFAAAAAA.toInt())
|
||||
.build()
|
||||
}
|
||||
}
|
@@ -19,7 +19,7 @@
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/12.
|
||||
*/
|
||||
@file:Suppress("unused", "DEPRECATION")
|
||||
@file:Suppress("unused", "DEPRECATION", "OPT_IN_USAGE", "EXPERIMENTAL_API_USAGE")
|
||||
|
||||
package com.fankes.apperrorstracking.utils.factory
|
||||
|
||||
@@ -29,16 +29,20 @@ import android.graphics.Color
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.graphics.drawable.InsetDrawable
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import com.highcapable.yukihookapi.annotation.CauseProblemsApi
|
||||
import com.highcapable.yukihookapi.hook.factory.method
|
||||
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
|
||||
|
||||
/**
|
||||
* 构造对话框
|
||||
@@ -55,13 +59,12 @@ class DialogBuilder(val context: Context) {
|
||||
private var instanceAndroidX: androidx.appcompat.app.AlertDialog.Builder? = null // 实例对象
|
||||
private var instanceAndroid: android.app.AlertDialog.Builder? = null // 实例对象
|
||||
|
||||
private var isSystemAlert = false // 标识为系统级别的对话框
|
||||
|
||||
private var onCancel: (() -> Unit)? = null // 对话框取消监听
|
||||
|
||||
private var dialogInstance: Dialog? = null // 对话框实例
|
||||
|
||||
private var customLayoutView: View? = null // 自定义布局
|
||||
@CauseProblemsApi
|
||||
var customLayoutView: View? = null // 自定义布局
|
||||
|
||||
/**
|
||||
* 是否需要使用 AndroidX 风格对话框
|
||||
@@ -89,15 +92,6 @@ class DialogBuilder(val context: Context) {
|
||||
else runCatching { instanceAndroid?.setCancelable(false) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置为系统级别对话框
|
||||
*
|
||||
* - ❗仅可在系统级别的 APP 中生效
|
||||
*/
|
||||
fun makeSystemAlert() {
|
||||
isSystemAlert = true
|
||||
}
|
||||
|
||||
/** 设置对话框标题 */
|
||||
var title
|
||||
get() = ""
|
||||
@@ -137,13 +131,15 @@ class DialogBuilder(val context: Context) {
|
||||
|
||||
/**
|
||||
* 设置对话框自定义布局
|
||||
* @return [customLayoutView]
|
||||
* @return [ViewBinding]
|
||||
*/
|
||||
var view
|
||||
get() = customLayoutView
|
||||
set(value) {
|
||||
customLayoutView = value
|
||||
}
|
||||
inline fun <reified T : ViewBinding> bind() =
|
||||
T::class.java.method {
|
||||
name = "inflate"
|
||||
param(LayoutInflaterClass)
|
||||
}.get().invoke<T>(LayoutInflater.from(context))?.apply {
|
||||
customLayoutView = root
|
||||
} ?: error("binding failed")
|
||||
|
||||
/**
|
||||
* 设置对话框确定按钮
|
||||
@@ -196,8 +192,6 @@ class DialogBuilder(val context: Context) {
|
||||
customLayoutView?.let { setView(it) }
|
||||
dialogInstance = this
|
||||
setOnCancelListener { onCancel?.invoke() }
|
||||
/** 只有 SystemUid 才能响应系统级别的对话框 */
|
||||
if (isSystemAlert) runCatching { window?.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT) }
|
||||
}?.show()
|
||||
} else runCatching {
|
||||
instanceAndroid?.create()?.apply {
|
||||
@@ -217,8 +211,6 @@ class DialogBuilder(val context: Context) {
|
||||
)
|
||||
dialogInstance = this
|
||||
setOnCancelListener { onCancel?.invoke() }
|
||||
/** 只有 SystemUid 才能响应系统级别的对话框 */
|
||||
if (isSystemAlert) runCatching { window?.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT) }
|
||||
}?.show()
|
||||
}
|
||||
}
|
||||
|
@@ -46,9 +46,14 @@ object FrameworkTool {
|
||||
private const val CALL_APP_ERRORS_DATA_REMOVE_RESULT = "call_app_errors_data_remove_result"
|
||||
private const val CALL_APP_ERRORS_DATA_CLEAR = "call_app_errors_data_clear"
|
||||
private const val CALL_APP_ERRORS_DATA_CLEAR_RESULT = "call_app_errors_data_clear_result"
|
||||
private const val CALL_IGNORED_ERRORS_IF_UNLOCK_RESULT = "call_ignored_errors_if_unlock_result"
|
||||
private const val CALL_IGNORED_ERRORS_IF_RESTART_RESULT = "call_ignored_errors_if_restart_result"
|
||||
|
||||
private val CALL_OPEN_SPECIFY_APP = ChannelData<String>("call_open_specify_app")
|
||||
private val CALL_APP_ERRORS_DATA_REMOVE = ChannelData<AppErrorsInfoBean>("call_app_errors_data_remove")
|
||||
private val CALL_APP_ERRORS_DATA_GET_RESULT = ChannelData<ArrayList<AppErrorsInfoBean>>("call_app_errors_data_get_result")
|
||||
private val CALL_IGNORED_ERRORS_IF_UNLOCK = ChannelData<String>("call_ignored_errors_if_unlock")
|
||||
private val CALL_IGNORED_ERRORS_IF_RESTART = ChannelData<String>("call_ignored_errors_if_restart")
|
||||
|
||||
/**
|
||||
* 宿主注册监听
|
||||
@@ -66,6 +71,12 @@ object FrameworkTool {
|
||||
*/
|
||||
fun with(instance: PackageParam, initiate: Host.() -> Unit) = apply { this.instance = instance }.apply(initiate)
|
||||
|
||||
/**
|
||||
* 监听使用系统框架打开 APP
|
||||
* @param result 回调包名
|
||||
*/
|
||||
fun onOpenAppUsedFramework(result: (String) -> Unit) = instance?.dataChannel?.wait(CALL_OPEN_SPECIFY_APP) { result(it) }
|
||||
|
||||
/**
|
||||
* 监听发送 APP 异常信息数组
|
||||
* @param result 回调数据
|
||||
@@ -99,6 +110,32 @@ object FrameworkTool {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听忽略 APP 的错误直到设备重新解锁
|
||||
* @param result 回调包名
|
||||
*/
|
||||
fun onIgnoredErrorsIfUnlock(result: (String) -> Unit) {
|
||||
instance?.dataChannel?.with {
|
||||
wait(CALL_IGNORED_ERRORS_IF_UNLOCK) {
|
||||
result(it)
|
||||
put(CALL_IGNORED_ERRORS_IF_UNLOCK_RESULT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听忽略 APP 的错误直到设备重新启动
|
||||
* @param result 回调包名
|
||||
*/
|
||||
fun onIgnoredErrorsIfRestart(result: (String) -> Unit) {
|
||||
instance?.dataChannel?.with {
|
||||
wait(CALL_IGNORED_ERRORS_IF_RESTART) {
|
||||
result(it)
|
||||
put(CALL_IGNORED_ERRORS_IF_RESTART_RESULT)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,6 +166,14 @@ object FrameworkTool {
|
||||
*/
|
||||
fun checkingActivated(context: Context, result: (Boolean) -> Unit) = context.dataChannel(SYSTEM_FRAMEWORK_NAME).checkingVersionEquals(result)
|
||||
|
||||
/**
|
||||
* 使用系统框架打开 [packageName]
|
||||
* @param context 实例
|
||||
* @param packageName APP 包名
|
||||
*/
|
||||
fun openAppUsedFramework(context: Context, packageName: String) =
|
||||
context.dataChannel(SYSTEM_FRAMEWORK_NAME).put(CALL_OPEN_SPECIFY_APP, packageName)
|
||||
|
||||
/**
|
||||
* 获取 APP 异常信息数组
|
||||
* @param context 实例
|
||||
@@ -165,4 +210,30 @@ object FrameworkTool {
|
||||
put(CALL_APP_ERRORS_DATA_CLEAR)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 忽略 [packageName] 的错误直到设备重新解锁
|
||||
* @param context 实例
|
||||
* @param packageName APP 包名
|
||||
* @param callback 成功后回调
|
||||
*/
|
||||
fun ignoredErrorsIfUnlock(context: Context, packageName: String, callback: () -> Unit) {
|
||||
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
|
||||
wait(CALL_IGNORED_ERRORS_IF_UNLOCK_RESULT) { callback() }
|
||||
put(CALL_IGNORED_ERRORS_IF_UNLOCK, packageName)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 忽略 [packageName] 的错误直到设备重新启动
|
||||
* @param context 实例
|
||||
* @param packageName APP 包名
|
||||
* @param callback 成功后回调
|
||||
*/
|
||||
fun ignoredErrorsIfRestart(context: Context, packageName: String, callback: () -> Unit) {
|
||||
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
|
||||
wait(CALL_IGNORED_ERRORS_IF_RESTART_RESULT) { callback() }
|
||||
put(CALL_IGNORED_ERRORS_IF_RESTART, packageName)
|
||||
}
|
||||
}
|
||||
}
|
4
app/src/main/res/layout/activity_app_errors_display.xml
Normal file
4
app/src/main/res/layout/activity_app_errors_display.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
141
app/src/main/res/layout/dia_app_errors_display.xml
Normal file
141
app/src/main/res/layout/dia_app_errors_display.xml
Normal file
@@ -0,0 +1,141 @@
|
||||
<?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:orientation="vertical"
|
||||
android:padding="10dp">
|
||||
|
||||
<com.fankes.apperrorstracking.ui.view.ItemLinearLayout
|
||||
android:id="@+id/app_info_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingVertical="15dp">
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:padding="1dp"
|
||||
android:src="@drawable/ic_baseline_info"
|
||||
app:tint="@color/colorTextGray" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_info"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
</com.fankes.apperrorstracking.ui.view.ItemLinearLayout>
|
||||
|
||||
<com.fankes.apperrorstracking.ui.view.ItemLinearLayout
|
||||
android:id="@+id/close_app_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingVertical="15dp">
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:src="@drawable/ic_baseline_close"
|
||||
app:tint="@color/colorTextGray" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/close_app"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
</com.fankes.apperrorstracking.ui.view.ItemLinearLayout>
|
||||
|
||||
<com.fankes.apperrorstracking.ui.view.ItemLinearLayout
|
||||
android:id="@+id/reopen_app_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingVertical="15dp">
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:src="@drawable/ic_baseline_refresh"
|
||||
app:tint="@color/colorTextGray" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reopen_app"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
</com.fankes.apperrorstracking.ui.view.ItemLinearLayout>
|
||||
|
||||
<com.fankes.apperrorstracking.ui.view.ItemLinearLayout
|
||||
android:id="@+id/error_detail_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingVertical="15dp">
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:src="@drawable/ic_baseline_bug_report"
|
||||
app:tint="@color/colorTextGray" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/error_detail"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
</com.fankes.apperrorstracking.ui.view.ItemLinearLayout>
|
||||
|
||||
<com.fankes.apperrorstracking.ui.view.ItemLinearLayout
|
||||
android:id="@+id/ignore_if_unlock_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingVertical="15dp">
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:src="@drawable/ic_baseline_eject"
|
||||
app:tint="@color/colorTextGray" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/ignore_if_unlock"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
</com.fankes.apperrorstracking.ui.view.ItemLinearLayout>
|
||||
|
||||
<com.fankes.apperrorstracking.ui.view.ItemLinearLayout
|
||||
android:id="@+id/ignore_if_restart_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingVertical="15dp">
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:src="@drawable/ic_baseline_eject"
|
||||
app:tint="@color/colorTextGray" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/ignore_if_restart"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
</com.fankes.apperrorstracking.ui.view.ItemLinearLayout>
|
||||
</LinearLayout>
|
@@ -1,6 +1,7 @@
|
||||
<resources>
|
||||
<string name="app_name">AppErrorsTracking</string>
|
||||
<string name="xposed_desc">Added more features to app\'s crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.</string>
|
||||
<string name="empty_lable" translatable="false" />
|
||||
<string name="app_info">App\'s Info</string>
|
||||
<string name="reopen_app">Reopen App</string>
|
||||
<string name="error_detail">Error Detail</string>
|
||||
|
@@ -13,4 +13,15 @@
|
||||
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
|
||||
<!-- Translucent application theme. -->
|
||||
<style name="Theme.AppErrorsTracking.Translucent" parent="Theme.AppErrorsTracking">
|
||||
<item name="android:background">@android:color/transparent</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:windowContentOverlay">@android:color/transparent</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowIsFloating">true</item>
|
||||
<item name="android:backgroundDimEnabled">false</item>
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
</style>
|
||||
</resources>
|
Reference in New Issue
Block a user