Modify merge YukiHookAPI new usage and compatible with API 33

This commit is contained in:
2022-10-01 03:32:23 +08:00
parent 1973c1acaf
commit 8d64f9c989
14 changed files with 190 additions and 188 deletions

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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()))
}

View File

@@ -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()
/** 初始化沉浸状态栏 */

View File

@@ -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) }
}

View File

@@ -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> {

View File

@@ -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

View File

@@ -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()

View File

@@ -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()

View File

@@ -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>() } }
/** 功能管理按钮点击事件 */

View File

@@ -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()
}
}
}

View File

@@ -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

View File

@@ -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() }

View File

@@ -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()
/** 初始化沉浸状态栏 */