mirror of
https://github.com/KitsunePie/AppErrorsTracking.git
synced 2025-09-01 08:45:16 +08:00
Modify merge YukiHookAPI new usage and compatible with API 33
This commit is contained in:
@@ -27,7 +27,6 @@ object DataConst {
|
||||
|
||||
val SHOW_DEVELOPER_NOTICE = PrefsData("_show_developer_notice", true)
|
||||
|
||||
val ENABLE_HIDE_ICON = PrefsData("_hide_icon", false)
|
||||
val ENABLE_ONLY_SHOW_ERRORS_IN_FRONT = PrefsData("_enable_only_show_errors_in_front", false)
|
||||
val ENABLE_ONLY_SHOW_ERRORS_IN_MAIN = PrefsData("_enable_only_show_errors_in_main", false)
|
||||
val ENABLE_ALWAYS_SHOWS_REOPEN_APP_OPTIONS = PrefsData("_enable_always_shows_reopen_app_options", false)
|
||||
|
@@ -32,7 +32,7 @@ import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit
|
||||
class HookEntry : IYukiHookXposedInit {
|
||||
|
||||
override fun onInit() = configs {
|
||||
debugTag = "AppErrorsTracking"
|
||||
debugLog { tag = "AppErrorsTracking" }
|
||||
isDebug = false
|
||||
isEnableModulePrefsCache = false
|
||||
}
|
||||
|
@@ -27,7 +27,6 @@ import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Message
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
@@ -94,7 +93,7 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
registerReceiver(Intent.ACTION_LOCALE_CHANGED) { _, _ -> refreshModuleAppResources() }
|
||||
}
|
||||
FrameworkTool.Host.with(instance = this) {
|
||||
onOpenAppUsedFramework { appContext.openApp(it) }
|
||||
onOpenAppUsedFramework { appContext?.openApp(it) }
|
||||
onPushAppErrorsInfoData { appErrorsRecords }
|
||||
onRemoveAppErrorsInfoData { appErrorsRecords.remove(it) }
|
||||
onClearAppErrorsInfoData { appErrorsRecords.clear() }
|
||||
@@ -119,19 +118,22 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
mutedErrorsIfRestartApps.clear()
|
||||
}
|
||||
onPushAppListData { filters ->
|
||||
arrayListOf<AppInfoBean>().apply {
|
||||
appContext.packageManager.getInstalledPackages(PackageManager.GET_CONFIGURATIONS).also { info ->
|
||||
(if (filters.name.isNotBlank())
|
||||
info.filter { it.packageName.contains(filters.name) || appContext.appName(it.packageName).contains(filters.name) }
|
||||
else info).let { result ->
|
||||
if (filters.isContainsSystem.not()) result.filter { (it.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) == 0 }
|
||||
else result
|
||||
}.sortedByDescending { it.lastUpdateTime }
|
||||
.forEach { add(AppInfoBean(name = appContext.appName(it.packageName), packageName = it.packageName)) }
|
||||
/** 移除模块自身 */
|
||||
removeIf { it.packageName == BuildConfig.APPLICATION_ID }
|
||||
appContext?.let { context ->
|
||||
arrayListOf<AppInfoBean>().apply {
|
||||
context.listOfPackages().also { info ->
|
||||
(if (filters.name.isNotBlank())
|
||||
info.filter { it.packageName.contains(filters.name) || context.appNameOf(it.packageName).contains(filters.name) }
|
||||
else info).let { result ->
|
||||
if (filters.isContainsSystem.not())
|
||||
result.filter { (it.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) == 0 }
|
||||
else result
|
||||
}.sortedByDescending { it.lastUpdateTime }
|
||||
.forEach { add(AppInfoBean(name = context.appNameOf(it.packageName), packageName = it.packageName)) }
|
||||
/** 移除模块自身 */
|
||||
removeIf { it.packageName == BuildConfig.APPLICATION_ID }
|
||||
}
|
||||
}
|
||||
}
|
||||
} ?: arrayListOf()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,48 +193,48 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
val errData = args().first().cast<Message>()?.obj
|
||||
|
||||
/** 当前进程信息 */
|
||||
val proc = AppErrorDialog_DataClass.clazz.field { name = "proc" }.get(errData).any()
|
||||
val proc = AppErrorDialog_DataClass.toClass().field { name = "proc" }.get(errData).any()
|
||||
|
||||
/** 当前 UserId 信息 */
|
||||
val userId = ProcessRecordClass.clazz.field { name = "userId" }.get(proc).int()
|
||||
val userId = ProcessRecordClass.toClass().field { name = "userId" }.get(proc).int()
|
||||
|
||||
/** 当前 APP 信息 */
|
||||
val appInfo = ProcessRecordClass.clazz.field { name = "info" }.get(proc).cast<ApplicationInfo>()
|
||||
val appInfo = ProcessRecordClass.toClass().field { name = "info" }.get(proc).cast<ApplicationInfo>()
|
||||
|
||||
/** 当前进程名称 */
|
||||
val processName = ProcessRecordClass.clazz.field { name = "processName" }.get(proc).string()
|
||||
val processName = ProcessRecordClass.toClass().field { name = "processName" }.get(proc).string()
|
||||
|
||||
/** 当前 APP、进程 包名 */
|
||||
val packageName = appInfo?.packageName ?: processName
|
||||
|
||||
/** 当前 APP 名称 */
|
||||
val appName = appInfo?.let { context.appName(it.packageName) } ?: packageName
|
||||
val appName = appInfo?.let { context.appNameOf(it.packageName) } ?: packageName
|
||||
|
||||
/** 是否为 APP */
|
||||
val isApp = (PackageListClass.clazz.method {
|
||||
val isApp = (PackageListClass.toClass().method {
|
||||
name = "size"
|
||||
emptyParam()
|
||||
}.get(if (ProcessRecordClass.clazz.hasMethod {
|
||||
}.get(if (ProcessRecordClass.toClass().hasMethod {
|
||||
name = "getPkgList"
|
||||
emptyParam()
|
||||
}) ProcessRecordClass.clazz.method {
|
||||
}) ProcessRecordClass.toClass().method {
|
||||
name = "getPkgList"
|
||||
emptyParam()
|
||||
}.get(proc).call() else ProcessRecordClass.clazz.field {
|
||||
}.get(proc).call() else ProcessRecordClass.toClass().field {
|
||||
name = "pkgList"
|
||||
}.get(proc).self).int() == 1 && appInfo != null)
|
||||
}.get(proc).any()).int() == 1 && appInfo != null)
|
||||
|
||||
/** 是否为主进程 */
|
||||
val isMainProcess = packageName == processName
|
||||
|
||||
/** 是否为后台进程 */
|
||||
val isBackgroundProcess = UserControllerClass.clazz.method { name = "getCurrentProfileIds" }
|
||||
.get(ActivityManagerServiceClass.clazz.field { name = "mUserController" }
|
||||
val isBackgroundProcess = UserControllerClass.toClass().method { name = "getCurrentProfileIds" }
|
||||
.get(ActivityManagerServiceClass.toClass().field { name = "mUserController" }
|
||||
.get(field { name = "mService" }.get(instance).any()).any())
|
||||
.invoke<IntArray>()?.takeIf { it.isNotEmpty() }?.any { it != userId } ?: false
|
||||
|
||||
/** 是否短时内重复错误 */
|
||||
val isRepeating = AppErrorDialog_DataClass.clazz.field { name = "repeating" }.get(errData).boolean()
|
||||
val isRepeating = AppErrorDialog_DataClass.toClass().field { name = "repeating" }.get(errData).boolean()
|
||||
|
||||
/** 崩溃标题 */
|
||||
val errorTitle = if (isRepeating) LocaleString.aerrRepeatedTitle(appName) else LocaleString.aerrTitle(appName)
|
||||
@@ -260,7 +262,7 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
channelName = LocaleString.appName,
|
||||
title = errorTitle,
|
||||
content = LocaleString.appErrorsTip,
|
||||
icon = IconCompat.createWithBitmap(R.mipmap.ic_notify.drawableOf(moduleAppResources).toBitmap()),
|
||||
icon = IconCompat.createWithBitmap(moduleAppResources.drawableOf(R.mipmap.ic_notify).toBitmap()),
|
||||
color = 0xFFFF6200.toInt(),
|
||||
intent = AppErrorsRecordActivity.intent()
|
||||
)
|
||||
@@ -299,7 +301,7 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
}
|
||||
afterHook {
|
||||
/** 当前 APP 信息 */
|
||||
val appInfo = ProcessRecordClass.clazz.field { name = "info" }.get(args().first().any()).cast<ApplicationInfo>()
|
||||
val appInfo = ProcessRecordClass.toClass().field { name = "info" }.get(args().first().any()).cast<ApplicationInfo>()
|
||||
/** 添加当前异常信息到第一位 */
|
||||
appErrorsRecords.add(0, AppErrorsInfoBean.clone(appInfo?.packageName, args().last().cast()))
|
||||
}
|
||||
|
@@ -32,9 +32,9 @@ 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.current
|
||||
import com.highcapable.yukihookapi.hook.factory.method
|
||||
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
|
||||
import java.lang.reflect.ParameterizedType
|
||||
|
||||
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
|
||||
@@ -43,15 +43,11 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
javaClass.genericSuperclass.also { type ->
|
||||
if (type is ParameterizedType) {
|
||||
binding = (type.actualTypeArguments[0] as Class<VB>).method {
|
||||
name = "inflate"
|
||||
param(LayoutInflaterClass)
|
||||
}.get().invoke<VB>(layoutInflater) ?: error("binding failed")
|
||||
setContentView(binding.root)
|
||||
} else error("binding but got wrong type")
|
||||
}
|
||||
binding = current().generic()?.argument()?.method {
|
||||
name = "inflate"
|
||||
param(LayoutInflaterClass)
|
||||
}?.get()?.invoke<VB>(layoutInflater) ?: error("binding failed")
|
||||
setContentView(binding.root)
|
||||
/** 隐藏系统的标题栏 */
|
||||
supportActionBar?.hide()
|
||||
/** 初始化沉浸状态栏 */
|
||||
|
@@ -58,7 +58,7 @@ class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
|
||||
private var stackTrace = ""
|
||||
|
||||
override fun onCreate() {
|
||||
val appErrorsInfo = runCatching { intent?.getSerializableExtra(EXTRA_APP_ERRORS_INFO) as? AppErrorsInfoBean }.getOrNull()
|
||||
val appErrorsInfo = runCatching { intent?.getSerializableExtraCompat<AppErrorsInfoBean>(EXTRA_APP_ERRORS_INFO) }.getOrNull()
|
||||
?: return toastAndFinish(name = "AppErrorsInfo")
|
||||
binding.appInfoItem.setOnClickListener { openSelfSetting(appErrorsInfo.packageName) }
|
||||
binding.titleBackIcon.setOnClickListener { onBackPressed() }
|
||||
@@ -83,10 +83,10 @@ class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
|
||||
putExtra(Intent.EXTRA_TEXT, appErrorsInfo.stackOutputShareContent)
|
||||
}, LocaleString.shareErrorStack))
|
||||
}
|
||||
binding.appIcon.setImageDrawable(appIcon(appErrorsInfo.packageName))
|
||||
binding.appNameText.text = appName(appErrorsInfo.packageName)
|
||||
binding.appVersionText.text = appVersion(appErrorsInfo.packageName)
|
||||
binding.appAbiText.text = appCpuAbi(appErrorsInfo.packageName).ifBlank { LocaleString.noCpuAbi }
|
||||
binding.appIcon.setImageDrawable(appIconOf(appErrorsInfo.packageName))
|
||||
binding.appNameText.text = appNameOf(appErrorsInfo.packageName)
|
||||
binding.appVersionText.text = appVersionBrandOf(appErrorsInfo.packageName)
|
||||
binding.appAbiText.text = appCpuAbiOf(appErrorsInfo.packageName).ifBlank { LocaleString.noCpuAbi }
|
||||
binding.jvmErrorPanel.isGone = appErrorsInfo.isNativeCrash
|
||||
binding.errorTypeIcon.setImageResource(if (appErrorsInfo.isNativeCrash) R.drawable.ic_cpp else R.drawable.ic_java)
|
||||
binding.errorInfoText.text = appErrorsInfo.exceptionMessage
|
||||
@@ -98,7 +98,7 @@ class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
|
||||
binding.errorRecordTimeText.text = appErrorsInfo.dateTime
|
||||
binding.errorStackText.text = appErrorsInfo.stackTrace
|
||||
binding.appPanelScrollView.setOnScrollChangeListener { _, _, y, _, _ ->
|
||||
binding.detailTitleText.text = if (y >= 30.dp(context = this)) appName(appErrorsInfo.packageName) else LocaleString.appName
|
||||
binding.detailTitleText.text = if (y >= 30.dp(context = this)) appNameOf(appErrorsInfo.packageName) else LocaleString.appName
|
||||
}
|
||||
binding.detailTitleText.setOnClickListener { binding.appPanelScrollView.smoothScrollTo(0, 0) }
|
||||
}
|
||||
|
@@ -28,10 +28,7 @@ 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.factory.*
|
||||
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
|
||||
|
||||
class AppErrorsDisplayActivity : BaseActivity<ActivityAppErrorsDisplayBinding>() {
|
||||
@@ -56,7 +53,7 @@ class AppErrorsDisplayActivity : BaseActivity<ActivityAppErrorsDisplayBinding>()
|
||||
override fun onCreate() {
|
||||
instance?.finish()
|
||||
instance = this
|
||||
val appErrorsDisplay = runCatching { intent?.getSerializableExtra(EXTRA_APP_ERRORS_DISPLAY) as? AppErrorsDisplayBean }.getOrNull()
|
||||
val appErrorsDisplay = runCatching { intent?.getSerializableExtraCompat<AppErrorsDisplayBean>(EXTRA_APP_ERRORS_DISPLAY) }.getOrNull()
|
||||
?: return toastAndFinish(name = "AppErrorsDisplay")
|
||||
/** 显示对话框 */
|
||||
showDialog<DiaAppErrorsDisplayBinding> {
|
||||
|
@@ -29,8 +29,8 @@ import com.fankes.apperrorstracking.databinding.ActivityAppErrorsMutedBinding
|
||||
import com.fankes.apperrorstracking.databinding.AdapterAppErrorsMutedBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
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.appIconOf
|
||||
import com.fankes.apperrorstracking.utils.factory.appNameOf
|
||||
import com.fankes.apperrorstracking.utils.factory.bindAdapter
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
|
||||
@@ -58,8 +58,8 @@ class AppErrorsMutedActivity : BaseActivity<ActivityAppErrorsMutedBinding>() {
|
||||
onBindDatas { listData }
|
||||
onBindViews<AdapterAppErrorsMutedBinding> { binding, position ->
|
||||
listData[position].also { bean ->
|
||||
binding.appIcon.setImageDrawable(appIcon(bean.packageName))
|
||||
binding.appNameText.text = appName(bean.packageName)
|
||||
binding.appIcon.setImageDrawable(appIconOf(bean.packageName))
|
||||
binding.appNameText.text = appNameOf(bean.packageName)
|
||||
binding.muteTypeText.text = when (bean.type) {
|
||||
MutedErrorsAppBean.MuteType.UNTIL_UNLOCKS -> LocaleString.muteIfUnlock
|
||||
MutedErrorsAppBean.MuteType.UNTIL_REBOOTS -> LocaleString.muteIfRestart
|
||||
|
@@ -94,8 +94,8 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
|
||||
title = LocaleString.appErrorsStatistics
|
||||
binding.totalErrorsUnitText.text = LocaleString.totalErrorsUnit(listData.size)
|
||||
binding.totalAppsUnitText.text = LocaleString.totalAppsUnit(it.size)
|
||||
binding.mostErrorsAppIcon.setImageDrawable(appIcon(mostAppPackageName))
|
||||
binding.mostErrorsAppText.text = appName(mostAppPackageName)
|
||||
binding.mostErrorsAppIcon.setImageDrawable(appIconOf(mostAppPackageName))
|
||||
binding.mostErrorsAppText.text = appNameOf(mostAppPackageName)
|
||||
binding.mostErrorsTypeText.text = mostErrorsType
|
||||
binding.totalPptOfErrorsText.text = "$pptCount%"
|
||||
confirmButton(LocaleString.gotIt)
|
||||
@@ -132,8 +132,8 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
|
||||
onBindDatas { listData }
|
||||
onBindViews<AdapterAppErrorsRecordBinding> { binding, position ->
|
||||
listData[position].also { bean ->
|
||||
binding.appIcon.setImageDrawable(appIcon(bean.packageName))
|
||||
binding.appNameText.text = appName(bean.packageName)
|
||||
binding.appIcon.setImageDrawable(appIconOf(bean.packageName))
|
||||
binding.appNameText.text = appNameOf(bean.packageName)
|
||||
binding.errorsTimeText.text = bean.crossTime
|
||||
binding.errorTypeIcon.setImageResource(if (bean.isNativeCrash) R.drawable.ic_cpp else R.drawable.ic_java)
|
||||
binding.errorTypeText.text = if (bean.isNativeCrash) "Native crash" else bean.exceptionClassName.simpleThwName()
|
||||
|
@@ -31,7 +31,7 @@ import com.fankes.apperrorstracking.databinding.DiaAppsFilterBinding
|
||||
import com.fankes.apperrorstracking.hook.factory.*
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.appIcon
|
||||
import com.fankes.apperrorstracking.utils.factory.appIconOf
|
||||
import com.fankes.apperrorstracking.utils.factory.bindAdapter
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
|
||||
@@ -48,7 +48,7 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
|
||||
private val listData = ArrayList<AppInfoBean>()
|
||||
|
||||
override fun onCreate() {
|
||||
binding.titleBackIcon.setOnClickListener { onBackPressed() }
|
||||
binding.titleBackIcon.setOnClickListener { finish() }
|
||||
binding.batchIcon.setOnClickListener {
|
||||
showDialog<DiaAppConfigBinding> {
|
||||
title = LocaleString.batchOperations
|
||||
@@ -164,7 +164,7 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
|
||||
Thread {
|
||||
it.takeIf { e -> e.isNotEmpty() }?.forEach { e ->
|
||||
listData.add(e)
|
||||
e.icon = appIcon(e.packageName)
|
||||
e.icon = appIconOf(e.packageName)
|
||||
}
|
||||
runOnUiThread {
|
||||
onChanged?.invoke()
|
||||
|
@@ -23,8 +23,6 @@
|
||||
|
||||
package com.fankes.apperrorstracking.ui.activity.main
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.core.view.isVisible
|
||||
import com.fankes.apperrorstracking.BuildConfig
|
||||
@@ -35,10 +33,7 @@ import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsMutedActivity
|
||||
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsRecordActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.navigate
|
||||
import com.fankes.apperrorstracking.utils.factory.openBrowser
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
import com.fankes.apperrorstracking.utils.factory.toast
|
||||
import com.fankes.apperrorstracking.utils.factory.*
|
||||
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
|
||||
import com.highcapable.yukihookapi.YukiHookAPI
|
||||
import com.highcapable.yukihookapi.hook.factory.modulePrefs
|
||||
@@ -59,17 +54,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
binding.onlyShowErrorsInMainProcessSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_ONLY_SHOW_ERRORS_IN_MAIN)
|
||||
binding.alwaysShowsReopenAppOptionsSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_ALWAYS_SHOWS_REOPEN_APP_OPTIONS)
|
||||
binding.enableAppsConfigsTemplateSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_APP_CONFIG_TEMPLATE)
|
||||
binding.hideIconInLauncherSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_HIDE_ICON)
|
||||
binding.mgrAppsConfigsTemplateButton.isVisible = modulePrefs.get(DataConst.ENABLE_APP_CONFIG_TEMPLATE)
|
||||
binding.hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b ->
|
||||
if (btn.isPressed.not()) return@setOnCheckedChangeListener
|
||||
modulePrefs.put(DataConst.ENABLE_HIDE_ICON, b)
|
||||
packageManager.setComponentEnabledSetting(
|
||||
ComponentName(packageName, "${BuildConfig.APPLICATION_ID}.Home"),
|
||||
if (b) PackageManager.COMPONENT_ENABLED_STATE_DISABLED else PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP
|
||||
)
|
||||
}
|
||||
binding.onlyShowErrorsInFrontSwitch.setOnCheckedChangeListener { btn, b ->
|
||||
if (btn.isPressed.not()) return@setOnCheckedChangeListener
|
||||
modulePrefs.put(DataConst.ENABLE_ONLY_SHOW_ERRORS_IN_FRONT, b)
|
||||
@@ -87,6 +72,12 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
binding.mgrAppsConfigsTemplateButton.isVisible = b
|
||||
modulePrefs.put(DataConst.ENABLE_APP_CONFIG_TEMPLATE, b)
|
||||
}
|
||||
/** 设置桌面图标显示隐藏 */
|
||||
binding.hideIconInLauncherSwitch.isChecked = isLauncherIconShowing.not()
|
||||
binding.hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b ->
|
||||
if (btn.isPressed.not()) return@setOnCheckedChangeListener
|
||||
hideOrShowLauncherIcon(b)
|
||||
}
|
||||
/** 管理应用配置模板按钮点击事件 */
|
||||
binding.mgrAppsConfigsTemplateButton.setOnClickListener { whenActivated { navigate<ConfigureActivity>() } }
|
||||
/** 功能管理按钮点击事件 */
|
||||
|
@@ -25,9 +25,6 @@ package com.fankes.apperrorstracking.utils.factory
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
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
|
||||
@@ -35,12 +32,13 @@ import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsDisplayActivity
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import com.highcapable.yukihookapi.YukiHookAPI
|
||||
import com.highcapable.yukihookapi.annotation.CauseProblemsApi
|
||||
import com.highcapable.yukihookapi.hook.factory.method
|
||||
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
|
||||
@@ -49,7 +47,7 @@ import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
|
||||
* 构造 [VB] 自定义 View 对话框
|
||||
* @param initiate 对话框方法体
|
||||
*/
|
||||
@JvmName(name = "showDialog-VB")
|
||||
@JvmName(name = "showDialog_Generics")
|
||||
inline fun <reified VB : ViewBinding> Context.showDialog(initiate: DialogBuilder<VB>.() -> Unit) =
|
||||
DialogBuilder<VB>(context = this, VB::class.java).apply(initiate).show()
|
||||
|
||||
@@ -66,12 +64,17 @@ inline fun Context.showDialog(initiate: DialogBuilder<*>.() -> Unit) = DialogBui
|
||||
*/
|
||||
class DialogBuilder<VB : ViewBinding>(val context: Context, private val bindingClass: Class<*>? = null) {
|
||||
|
||||
private var instanceAndroidX: androidx.appcompat.app.AlertDialog.Builder? = null // 实例对象
|
||||
private var instanceAndroid: android.app.AlertDialog.Builder? = null // 实例对象
|
||||
/** 实例对象 */
|
||||
private var instance: AlertDialog.Builder? = null
|
||||
|
||||
private var onCancel: (() -> Unit)? = null // 对话框取消监听
|
||||
private var dialogInstance: Dialog? = null // 对话框实例
|
||||
private var customLayoutView: View? = null // 自定义布局
|
||||
/** 对话框取消监听 */
|
||||
private var onCancel: (() -> Unit)? = null
|
||||
|
||||
/** 对话框实例 */
|
||||
private var dialogInstance: Dialog? = null
|
||||
|
||||
/** 自定义布局 */
|
||||
private var customLayoutView: View? = null
|
||||
|
||||
/**
|
||||
* 获取 [DialogBuilder] 绑定布局对象
|
||||
@@ -86,49 +89,31 @@ class DialogBuilder<VB : ViewBinding>(val context: Context, private val bindingC
|
||||
} ?: error("This dialog maybe not a custom view dialog")
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要使用 AndroidX 风格对话框
|
||||
* @return [Boolean]
|
||||
*/
|
||||
private val isUsingAndroidX get() = runCatching { context is AppCompatActivity }.getOrNull() ?: false
|
||||
|
||||
init {
|
||||
if (isUsingAndroidX) runCatching {
|
||||
instanceAndroidX = MaterialAlertDialogBuilder(context).also { builder ->
|
||||
if (context is AppErrorsDisplayActivity)
|
||||
builder.background = (builder.background as MaterialShapeDrawable).apply { setCornerSize(15.dpFloat(context)) }
|
||||
}
|
||||
} else runCatching {
|
||||
instanceAndroid = android.app.AlertDialog.Builder(
|
||||
context,
|
||||
if (context.isSystemInDarkMode) android.R.style.Theme_Material_Dialog else android.R.style.Theme_Material_Light_Dialog
|
||||
)
|
||||
if (YukiHookAPI.Status.isXposedEnvironment) error("This dialog is not allowed to created in Xposed environment")
|
||||
instance = MaterialAlertDialogBuilder(context).also { builder ->
|
||||
if (context is AppErrorsDisplayActivity)
|
||||
builder.background = (builder.background as MaterialShapeDrawable).apply { setCornerSize(15.dpFloat(context)) }
|
||||
}
|
||||
}
|
||||
|
||||
/** 设置对话框不可关闭 */
|
||||
fun noCancelable() {
|
||||
if (isUsingAndroidX)
|
||||
runCatching { instanceAndroidX?.setCancelable(false) }
|
||||
else runCatching { instanceAndroid?.setCancelable(false) }
|
||||
instance?.setCancelable(false)
|
||||
}
|
||||
|
||||
/** 设置对话框标题 */
|
||||
var title
|
||||
get() = ""
|
||||
set(value) {
|
||||
if (isUsingAndroidX)
|
||||
runCatching { instanceAndroidX?.setTitle(value) }
|
||||
else runCatching { instanceAndroid?.setTitle(value) }
|
||||
instance?.setTitle(value)
|
||||
}
|
||||
|
||||
/** 设置对话框消息内容 */
|
||||
var msg
|
||||
get() = ""
|
||||
set(value) {
|
||||
if (isUsingAndroidX)
|
||||
runCatching { instanceAndroidX?.setMessage(value) }
|
||||
else runCatching { instanceAndroid?.setMessage(value) }
|
||||
instance?.setMessage(value)
|
||||
}
|
||||
|
||||
/** 设置进度条对话框消息内容 */
|
||||
@@ -156,9 +141,7 @@ class DialogBuilder<VB : ViewBinding>(val context: Context, private val bindingC
|
||||
* @param callback 点击事件
|
||||
*/
|
||||
fun confirmButton(text: String = LocaleString.confirm, callback: () -> Unit = {}) {
|
||||
if (isUsingAndroidX)
|
||||
runCatching { instanceAndroidX?.setPositiveButton(text) { _, _ -> callback() } }
|
||||
else runCatching { instanceAndroid?.setPositiveButton(text) { _, _ -> callback() } }
|
||||
instance?.setPositiveButton(text) { _, _ -> callback() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,9 +150,7 @@ class DialogBuilder<VB : ViewBinding>(val context: Context, private val bindingC
|
||||
* @param callback 点击事件
|
||||
*/
|
||||
fun cancelButton(text: String = LocaleString.cancel, callback: () -> Unit = {}) {
|
||||
if (isUsingAndroidX)
|
||||
runCatching { instanceAndroidX?.setNegativeButton(text) { _, _ -> callback() } }
|
||||
else runCatching { instanceAndroid?.setNegativeButton(text) { _, _ -> callback() } }
|
||||
instance?.setNegativeButton(text) { _, _ -> callback() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,9 +159,7 @@ class DialogBuilder<VB : ViewBinding>(val context: Context, private val bindingC
|
||||
* @param callback 点击事件
|
||||
*/
|
||||
fun neutralButton(text: String = LocaleString.more, callback: () -> Unit = {}) {
|
||||
if (isUsingAndroidX)
|
||||
runCatching { instanceAndroidX?.setNeutralButton(text) { _, _ -> callback() } }
|
||||
else runCatching { instanceAndroid?.setNeutralButton(text) { _, _ -> callback() } }
|
||||
instance?.setNeutralButton(text) { _, _ -> callback() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -199,31 +178,12 @@ class DialogBuilder<VB : ViewBinding>(val context: Context, private val bindingC
|
||||
fun show() {
|
||||
/** 若当前自定义 View 的对话框没有调用 [binding] 将会对其手动调用一次以确保显示布局 */
|
||||
if (bindingClass != null) binding
|
||||
if (isUsingAndroidX) runCatching {
|
||||
instanceAndroidX?.create()?.apply {
|
||||
runCatching {
|
||||
instance?.create()?.apply {
|
||||
customLayoutView?.let { setView(it) }
|
||||
dialogInstance = this
|
||||
setOnCancelListener { onCancel?.invoke() }
|
||||
}?.show()
|
||||
} else runCatching {
|
||||
instanceAndroid?.create()?.apply {
|
||||
customLayoutView?.let { setView(it) }
|
||||
window?.setBackgroundDrawable(
|
||||
InsetDrawable(
|
||||
GradientDrawable(
|
||||
GradientDrawable.Orientation.TOP_BOTTOM,
|
||||
if (context.isSystemInDarkMode) intArrayOf(0xFF2D2D2D.toInt(), 0xFF2D2D2D.toInt())
|
||||
else intArrayOf(Color.WHITE, Color.WHITE)
|
||||
).apply {
|
||||
shape = GradientDrawable.RECTANGLE
|
||||
gradientType = GradientDrawable.LINEAR_GRADIENT
|
||||
cornerRadius = 15.dpFloat(this@DialogBuilder.context)
|
||||
}, 30.dp(context), 0, 30.dp(context), 0
|
||||
)
|
||||
)
|
||||
dialogInstance = this
|
||||
setOnCancelListener { onCancel?.invoke() }
|
||||
}?.show()
|
||||
}
|
||||
}
|
||||
}
|
@@ -19,22 +19,27 @@
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/7.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION", "PrivateApi", "unused", "ObsoleteSdkInt")
|
||||
@file:Suppress("unused")
|
||||
|
||||
package com.fankes.apperrorstracking.utils.factory
|
||||
|
||||
import android.app.*
|
||||
import android.content.*
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.PackageInfoFlags
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import com.fankes.apperrorstracking.BuildConfig
|
||||
@@ -44,6 +49,7 @@ import com.google.android.material.snackbar.Snackbar
|
||||
import com.highcapable.yukihookapi.hook.factory.field
|
||||
import com.highcapable.yukihookapi.hook.type.android.ApplicationInfoClass
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import java.io.Serializable
|
||||
import java.math.RoundingMode
|
||||
import java.text.DecimalFormat
|
||||
|
||||
@@ -74,54 +80,86 @@ fun Number.dp(context: Context) = dpFloat(context).toInt()
|
||||
fun Number.dpFloat(context: Context) = toFloat() * context.resources.displayMetrics.density
|
||||
|
||||
/**
|
||||
* 从 [Int] resId 获取 [Drawable]
|
||||
* @param resources 使用的 [Resources]
|
||||
* 获取 [Drawable]
|
||||
* @param resId 属性资源 ID
|
||||
* @return [Drawable]
|
||||
*/
|
||||
fun Int.drawableOf(resources: Resources) = ResourcesCompat.getDrawable(resources, this, null) ?: error("Invalid resources")
|
||||
fun Resources.drawableOf(@DrawableRes resId: Int) = ResourcesCompat.getDrawable(this, resId, null) ?: error("Invalid resources")
|
||||
|
||||
/**
|
||||
* 获取 APP 名称
|
||||
* @param packageName 包名
|
||||
* @return [String]
|
||||
* 得到 APP 安装包信息 (兼容)
|
||||
* @param packageName APP 包名
|
||||
* @param flag [PackageInfoFlags]
|
||||
* @return [PackageInfo] or null
|
||||
*/
|
||||
fun Context.appName(packageName: String) =
|
||||
runCatching {
|
||||
packageManager.getPackageInfo(packageName, PackageManager.GET_META_DATA)
|
||||
.applicationInfo.loadLabel(packageManager).toString()
|
||||
}.getOrNull() ?: packageName
|
||||
private fun Context.getPackageInfoCompat(packageName: String, flag: Number = 0) = runCatching {
|
||||
@Suppress("DEPRECATION")
|
||||
if (Build.VERSION.SDK_INT >= 33)
|
||||
packageManager?.getPackageInfo(packageName, PackageInfoFlags.of(flag.toLong()))
|
||||
else packageManager?.getPackageInfo(packageName, flag.toInt())
|
||||
}.getOrNull()
|
||||
|
||||
/**
|
||||
* 获取 APP 完整版本
|
||||
* @param packageName 包名
|
||||
* 得到 APP 版本号 (兼容 [PackageInfo.getLongVersionCode])
|
||||
* @return [Int]
|
||||
*/
|
||||
private val PackageInfo.versionCodeCompat get() = PackageInfoCompat.getLongVersionCode(this)
|
||||
|
||||
/**
|
||||
* 获取系统中全部已安装应用列表
|
||||
* @return [List]<[PackageInfo]>
|
||||
*/
|
||||
fun Context.listOfPackages() = runCatching {
|
||||
@Suppress("DEPRECATION")
|
||||
if (Build.VERSION.SDK_INT >= 33)
|
||||
packageManager?.getInstalledPackages(PackageInfoFlags.of(PackageManager.GET_CONFIGURATIONS.toLong()))
|
||||
else packageManager?.getInstalledPackages(PackageManager.GET_CONFIGURATIONS)
|
||||
}.getOrNull() ?: emptyList()
|
||||
|
||||
/**
|
||||
* 得到 APP 名称
|
||||
* @param packageName APP 包名 - 默认为当前 APP
|
||||
* @return [String]
|
||||
*/
|
||||
fun Context.appVersion(packageName: String) =
|
||||
runCatching {
|
||||
packageManager.getPackageInfo(packageName, PackageManager.GET_META_DATA)?.let { "${it.versionName} (${it.versionCode})" }
|
||||
}.getOrNull() ?: "<unknown>"
|
||||
fun Context.appNameOf(packageName: String = getPackageName()) =
|
||||
getPackageInfoCompat(packageName)?.applicationInfo?.loadLabel(packageManager)?.toString() ?: ""
|
||||
|
||||
/**
|
||||
* 得到 APP 版本信息与版本号
|
||||
* @param packageName APP 包名 - 默认为当前 APP
|
||||
* @return [String] 无法获取时返回 "unknown"
|
||||
*/
|
||||
fun Context.appVersionBrandOf(packageName: String = getPackageName()) =
|
||||
getPackageInfoCompat(packageName)?.let { "${it.versionName}(${it.versionCodeCompat})" } ?: "unknown"
|
||||
|
||||
/**
|
||||
* 获取 APP CPU ABI 名称
|
||||
* @param packageName 包名
|
||||
* @param packageName APP 包名 - 默认为当前 APP
|
||||
* @return [String]
|
||||
*/
|
||||
fun Context.appCpuAbi(packageName: String) =
|
||||
runCatching {
|
||||
ApplicationInfoClass.field { name = "primaryCpuAbi" }
|
||||
.get(packageManager.getPackageInfo(packageName, PackageManager.GET_META_DATA)?.applicationInfo).string()
|
||||
}.getOrNull() ?: ""
|
||||
fun Context.appCpuAbiOf(packageName: String = getPackageName()) = runCatching {
|
||||
ApplicationInfoClass.field { name = "primaryCpuAbi" }.get(getPackageInfoCompat(packageName)?.applicationInfo).string()
|
||||
}.getOrNull() ?: ""
|
||||
|
||||
/**
|
||||
* 获取 APP 图标
|
||||
* @param packageName 包名
|
||||
* @return [Drawable]
|
||||
* 得到 APP 图标
|
||||
* @param packageName APP 包名 - 默认为当前 APP
|
||||
* @return [Drawable] 无发获取时返回 [R.drawable.ic_android]
|
||||
*/
|
||||
fun Context.appIcon(packageName: String) =
|
||||
runCatching {
|
||||
packageManager.getPackageInfo(packageName, PackageManager.GET_META_DATA)
|
||||
.applicationInfo.loadIcon(packageManager)
|
||||
}.getOrNull() ?: R.drawable.ic_android.drawableOf(resources)
|
||||
fun Context.appIconOf(packageName: String = getPackageName()) =
|
||||
getPackageInfoCompat(packageName)?.applicationInfo?.loadIcon(packageManager) ?: resources.drawableOf(R.drawable.ic_android)
|
||||
|
||||
/**
|
||||
* 获取 [Serializable] (兼容)
|
||||
* @param key 键值名称
|
||||
* @return [T] or null
|
||||
*/
|
||||
inline fun <reified T : Serializable> Intent.getSerializableExtraCompat(key: String): T? {
|
||||
@Suppress("DEPRECATION")
|
||||
return if (Build.VERSION.SDK_INT >= 33)
|
||||
getSerializableExtra(key, T::class.java)
|
||||
else getSerializableExtra(key) as? T?
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算与当前时间戳相差的友好时间
|
||||
@@ -308,4 +346,27 @@ fun execShell(cmd: String, isSu: Boolean = true) = runCatching {
|
||||
(if (isSu) Shell.su(cmd) else Shell.sh(cmd)).exec().out.let {
|
||||
if (it.isNotEmpty()) it[0].trim() else ""
|
||||
}
|
||||
}.getOrNull() ?: ""
|
||||
}.getOrNull() ?: ""
|
||||
|
||||
/**
|
||||
* 隐藏或显示启动器图标
|
||||
*
|
||||
* - 你可能需要 LSPosed 的最新版本以开启高版本系统中隐藏 APP 桌面图标功能
|
||||
* @param isShow 是否显示
|
||||
*/
|
||||
fun Context.hideOrShowLauncherIcon(isShow: Boolean) {
|
||||
packageManager?.setComponentEnabledSetting(
|
||||
ComponentName(packageName, "${BuildConfig.APPLICATION_ID}.Home"),
|
||||
if (isShow) PackageManager.COMPONENT_ENABLED_STATE_DISABLED else PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取启动器图标状态
|
||||
* @return [Boolean] 是否显示
|
||||
*/
|
||||
val Context.isLauncherIconShowing
|
||||
get() = packageManager?.getComponentEnabledSetting(
|
||||
ComponentName(packageName, "${BuildConfig.APPLICATION_ID}.Home")
|
||||
) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|
@@ -31,7 +31,7 @@ import com.fankes.apperrorsdemo.ui.activity.base.BaseActivity
|
||||
class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
|
||||
override fun onCreate() {
|
||||
binding.titleBackIcon.setOnClickListener { onBackPressed() }
|
||||
binding.titleBackIcon.setOnClickListener { finish() }
|
||||
binding.throwRuntimeButton.setOnClickListener { Channel.throwRuntimeException() }
|
||||
binding.throwIllegalStateButton.setOnClickListener { Channel.throwIllegalStateException() }
|
||||
binding.throwNullPointerButton.setOnClickListener { Channel.throwNullPointerException() }
|
||||
|
@@ -31,9 +31,9 @@ import androidx.core.view.WindowCompat
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.fankes.apperrorsdemo.R
|
||||
import com.fankes.apperrorsdemo.utils.factory.isNotSystemInDarkMode
|
||||
import com.highcapable.yukihookapi.hook.factory.current
|
||||
import com.highcapable.yukihookapi.hook.factory.method
|
||||
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
|
||||
import java.lang.reflect.ParameterizedType
|
||||
|
||||
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
|
||||
@@ -42,15 +42,11 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
javaClass.genericSuperclass.also { type ->
|
||||
if (type is ParameterizedType) {
|
||||
binding = (type.actualTypeArguments[0] as Class<VB>).method {
|
||||
name = "inflate"
|
||||
param(LayoutInflaterClass)
|
||||
}.get().invoke<VB>(layoutInflater) ?: error("binding failed")
|
||||
setContentView(binding.root)
|
||||
} else error("binding but got wrong type")
|
||||
}
|
||||
binding = current().generic()?.argument()?.method {
|
||||
name = "inflate"
|
||||
param(LayoutInflaterClass)
|
||||
}?.get()?.invoke<VB>(layoutInflater) ?: error("binding failed")
|
||||
setContentView(binding.root)
|
||||
/** 隐藏系统的标题栏 */
|
||||
supportActionBar?.hide()
|
||||
/** 初始化沉浸状态栏 */
|
||||
|
Reference in New Issue
Block a user