diff --git a/demo-app/build.gradle.kts b/demo-app/build.gradle.kts index cb130f3..2194a81 100644 --- a/demo-app/build.gradle.kts +++ b/demo-app/build.gradle.kts @@ -84,7 +84,8 @@ androidComponents { dependencies { implementation(com.fankes.projectpromote.project.promote) - implementation(com.highcapable.yukireflection.api) + implementation(com.highcapable.kavaref.kavaref.core) + implementation(com.highcapable.kavaref.kavaref.extension) implementation(androidx.core.core.ktx) implementation(androidx.appcompat.appcompat) implementation(com.google.android.material.material) diff --git a/demo-app/src/main/java/com/fankes/apperrorsdemo/ui/activity/base/BaseActivity.kt b/demo-app/src/main/java/com/fankes/apperrorsdemo/ui/activity/base/BaseActivity.kt index d78fe0d..1d2a396 100644 --- a/demo-app/src/main/java/com/fankes/apperrorsdemo/ui/activity/base/BaseActivity.kt +++ b/demo-app/src/main/java/com/fankes/apperrorsdemo/ui/activity/base/BaseActivity.kt @@ -19,21 +19,20 @@ * * This file is created by fankes on 2022/5/10. */ -@file:Suppress("DEPRECATION") - package com.fankes.apperrorsdemo.ui.activity.base import android.os.Build import android.os.Bundle +import android.view.LayoutInflater import androidx.appcompat.app.AppCompatActivity import androidx.core.content.res.ResourcesCompat import androidx.core.view.WindowCompat import androidx.viewbinding.ViewBinding import com.fankes.apperrorsdemo.R import com.fankes.apperrorsdemo.utils.factory.isNotSystemInDarkMode -import com.highcapable.yukireflection.factory.current -import com.highcapable.yukireflection.factory.method -import com.highcapable.yukireflection.type.android.LayoutInflaterClass +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.kavaref.extension.genericSuperclassTypeArguments +import com.highcapable.kavaref.extension.toClassOrNull abstract class BaseActivity : AppCompatActivity() { @@ -42,10 +41,11 @@ abstract class BaseActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = current().generic()?.argument()?.method { + val bindingClass = javaClass.genericSuperclassTypeArguments().firstOrNull()?.toClassOrNull() + binding = bindingClass?.resolve()?.optional()?.firstMethodOrNull { name = "inflate" - param(LayoutInflaterClass) - }?.get()?.invoke(layoutInflater) ?: error("binding failed") + parameters(LayoutInflater::class) + }?.invoke(layoutInflater) ?: error("binding failed") if (Build.VERSION.SDK_INT >= 35) binding.root.fitsSystemWindows = true setContentView(binding.root) /** 隐藏系统的标题栏 */ @@ -55,6 +55,7 @@ abstract class BaseActivity : AppCompatActivity() { isAppearanceLightStatusBars = isNotSystemInDarkMode isAppearanceLightNavigationBars = isNotSystemInDarkMode } + @Suppress("DEPRECATION") ResourcesCompat.getColor(resources, R.color.colorThemeBackground, null).also { window?.statusBarColor = it window?.navigationBarColor = it diff --git a/gradle/sweet-dependency/sweet-dependency-config.yaml b/gradle/sweet-dependency/sweet-dependency-config.yaml index 04831f8..2872be0 100644 --- a/gradle/sweet-dependency/sweet-dependency-config.yaml +++ b/gradle/sweet-dependency/sweet-dependency-config.yaml @@ -22,7 +22,7 @@ repositories: plugins: com.android.application: alias: android-application - version: 8.9.0 + version: 8.9.3 org.jetbrains.kotlin.android: alias: kotlin-android version: 2.1.10 @@ -46,12 +46,14 @@ libraries: rovo89-xposed-api com.highcapable.yukihookapi: api: - version: 1.2.1 + version: 1.3.0 ksp-xposed: version-ref: ::api - com.highcapable.yukireflection: - api: - version: 1.0.3 + com.highcapable.kavaref: + kavaref-core: + version: 1.0.0 + kavaref-extension: + version: 1.0.0 com.microsoft.appcenter: appcenter-analytics: version: 5.0.6 @@ -69,13 +71,13 @@ libraries: version: 2.12.1 com.squareup.okhttp3: okhttp: - version: 5.0.0-alpha.14 + version: 5.0.0-alpha.16 androidx.core: core-ktx: - version: 1.15.0 + version: 1.16.0 androidx.appcompat: appcompat: - version: 1.7.0 + version: 1.7.1 com.google.android.material: material: version: 1.12.0 diff --git a/module-app/build.gradle.kts b/module-app/build.gradle.kts index 1943cfe..7f8af90 100644 --- a/module-app/build.gradle.kts +++ b/module-app/build.gradle.kts @@ -82,6 +82,8 @@ dependencies { compileOnly(de.robv.android.xposed.api) implementation(com.highcapable.yukihookapi.api) ksp(com.highcapable.yukihookapi.ksp.xposed) + implementation(com.highcapable.kavaref.kavaref.core) + implementation(com.highcapable.kavaref.kavaref.extension) implementation(com.fankes.projectpromote.project.promote) implementation(com.microsoft.appcenter.appcenter.analytics) implementation(com.microsoft.appcenter.appcenter.crashes) diff --git a/module-app/src/main/java/com/fankes/apperrorstracking/hook/entity/FrameworkHooker.kt b/module-app/src/main/java/com/fankes/apperrorstracking/hook/entity/FrameworkHooker.kt index 63ef0c8..fb7b0f7 100644 --- a/module-app/src/main/java/com/fankes/apperrorstracking/hook/entity/FrameworkHooker.kt +++ b/module-app/src/main/java/com/fankes/apperrorstracking/hook/entity/FrameworkHooker.kt @@ -30,6 +30,7 @@ import android.content.Intent import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo import android.os.Build +import android.os.Bundle import android.os.Message import android.os.SystemClock import android.util.ArrayMap @@ -58,16 +59,10 @@ import com.fankes.apperrorstracking.utils.factory.toArrayList import com.fankes.apperrorstracking.utils.factory.toast import com.fankes.apperrorstracking.utils.tool.FrameworkTool import com.fankes.apperrorstracking.wrapper.BuildConfigWrapper -import com.highcapable.yukihookapi.hook.bean.VariousClass +import com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.kavaref.extension.VariousClass import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker -import com.highcapable.yukihookapi.hook.factory.constructor -import com.highcapable.yukihookapi.hook.factory.field -import com.highcapable.yukihookapi.hook.factory.hasMethod -import com.highcapable.yukihookapi.hook.factory.method import com.highcapable.yukihookapi.hook.log.YLog -import com.highcapable.yukihookapi.hook.type.android.BundleClass -import com.highcapable.yukihookapi.hook.type.android.MessageClass -import com.highcapable.yukihookapi.hook.type.java.BooleanType object FrameworkHooker : YukiBaseHooker() { @@ -110,40 +105,73 @@ object FrameworkHooker : YukiBaseHooker() { * 获取当前包列表实例 * @return [Any] or null */ - private val pkgList = if (ProcessRecordClass.hasMethod { name = "getPkgList"; emptyParam() }) - ProcessRecordClass.method { name = "getPkgList"; emptyParam() }.get(proc).call() - else ProcessRecordClass.field { name { it.endsWith("pkgList", true) } }.get(proc).any() + private val pkgList by lazy { + ProcessRecordClass.resolve().optional(silent = true) + .firstMethodOrNull { + name = "getPkgList" + emptyParameters() + }?.of(proc)?.invoke() + ?: ProcessRecordClass.resolve().optional(silent = true) + .firstFieldOrNull { + name { it.endsWith("pkgList", true) } + }?.of(proc)?.get() + } /** * 获取当前包列表数组大小 * @return [Int] */ - private val pkgListSize = PackageListClass?.method { name = "size"; emptyParam() }?.get(pkgList)?.int() - ?: ProcessRecordClass.field { name = "pkgList" }.get(proc).cast>()?.size ?: -1 + private val pkgListSize by lazy { + PackageListClass?.resolve()?.optional(silent = true) + ?.firstMethodOrNull { + name = "size" + emptyParameters() + }?.of(pkgList)?.invoke() + ?: ProcessRecordClass.resolve().optional(silent = true) + .firstFieldOrNull { name = "pkgList" } + ?.of(proc)?.get>()?.size ?: -1 + } /** * 获取当前 pid 信息 * @return [Int] */ - val pid = ProcessRecordClass.field { name { it == "mPid" || it == "pid" } }.get(proc).int() + val pid by lazy { + ProcessRecordClass.resolve().optional() + .firstFieldOrNull { + name { it == "mPid" || it == "pid" } + }?.of(proc)?.get() ?: 0 + } /** * 获取当前用户 ID 信息 * @return [Int] */ - val userId = ProcessRecordClass.field { name = "userId" }.get(proc).int() + val userId by lazy { + ProcessRecordClass.resolve().optional() + .firstFieldOrNull { name = "userId" } + ?.of(proc)?.get() ?: 0 + } /** * 获取当前 APP 信息 * @return [ApplicationInfo] or null */ - val appInfo = ProcessRecordClass.field { name = "info" }.get(proc).cast() + val appInfo by lazy { + ProcessRecordClass.resolve().optional() + .firstFieldOrNull { name = "info" } + ?.of(proc)?.get() + } /** * 获取当前进程名称 * @return [String] */ - val processName = ProcessRecordClass.field { name = "processName" }.get(proc).string() + val processName by lazy { + ProcessRecordClass.resolve().optional() + .firstFieldOrNull { name = "processName" } + ?.of(proc)?.get() ?: "" + } /** * 获取当前 APP、进程 包名 @@ -167,17 +195,25 @@ object FrameworkHooker : YukiBaseHooker() { * 获取当前进程是否为后台进程 * @return [Boolean] */ - val isBackgroundProcess = UserControllerClass - .method { name { it == "getCurrentProfileIds" || it == "getCurrentProfileIdsLocked" } } - .get(ActivityManagerServiceClass?.field { name = "mUserController" } - ?.get(AppErrorsClass.field { name = "mService" }.get(errors).any())?.any()) - .invoke()?.takeIf { it.isNotEmpty() }?.any { it != userId } ?: false + val isBackgroundProcess by lazy { + UserControllerClass.resolve().optional() + .firstMethodOrNull { name { it == "getCurrentProfileIds" || it == "getCurrentProfileIdsLocked" } } + ?.of(ActivityManagerServiceClass?.resolve()?.optional()?.firstFieldOrNull { name = "mUserController" } + ?.of(AppErrorsClass.resolve().optional().firstFieldOrNull { name = "mService" }?.of(errors)?.get())?.getQuietly()) + ?.invokeQuietly()?.takeIf { it.isNotEmpty() }?.any { it != userId } ?: false + } /** * 获取当前进程是否短时内重复崩溃 * @return [Boolean] */ - val isRepeatingCrash = resultData?.let { AppErrorDialog_DataClass.field { name = "repeating" }.get(it).boolean() } ?: false + val isRepeatingCrash by lazy { + resultData?.let { + AppErrorDialog_DataClass.resolve().optional() + .firstFieldOrNull { name = "repeating" } + ?.of(it)?.get() == true + } ?: false + } } /** 注册生命周期 */ @@ -376,60 +412,56 @@ object FrameworkHooker : YukiBaseHooker() { /** 注册生命周期 */ registerLifecycle() /** 干掉原生错误对话框 - 如果有 */ - ErrorDialogControllerClass?.apply { - if (hasMethod { name = "hasCrashDialogs"; emptyParam() }) { - method { - name = "hasCrashDialogs" - emptyParam() - }.hook().replaceToTrue() - - } else { - constructor { - paramCount = 1 - }.hook().after { - field { name = "mCrashDialogs" }.get(instance).set(emptyList()) + ErrorDialogControllerClass?.resolve()?.optional(silent = true)?.apply { + val hasCrashDialogs = firstMethodOrNull { + name = "hasCrashDialogs" + emptyParameters() + }?.hook()?.replaceToTrue() != null + if (!hasCrashDialogs) + firstConstructorOrNull { + parameterCount = 1 + }?.hook()?.after { + firstFieldOrNull { name = "mCrashDialogs" }?.of(instance)?.set(emptyList()) } - } - - method { + firstMethodOrNull { name = "showCrashDialogs" - paramCount = 1 - }.hook().intercept() + parameterCount = 1 + }?.hook()?.intercept() } /** 干掉原生错误对话框 - API 30 以下 */ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { - ActivityTaskManagerService_LocalServiceClass?.method { + ActivityTaskManagerService_LocalServiceClass?.resolve()?.optional()?.firstMethodOrNull { name = "canShowErrorDialogs" - emptyParam() - }?.ignored()?.hook()?.replaceToFalse() - ActivityManagerServiceClass?.method { + emptyParameters() + }?.hook()?.replaceToFalse() + ActivityManagerServiceClass?.resolve()?.optional()?.firstMethodOrNull { name = "canShowErrorDialogs" - emptyParam() - }?.ignored()?.hook()?.replaceToFalse() + emptyParameters() + }?.hook()?.replaceToFalse() } /** 干掉原生错误对话框 - 如果上述方法全部失效则直接结束对话框 */ - AppErrorDialogClass.apply { - method { + AppErrorDialogClass.resolve().optional(silent = true).apply { + firstMethodOrNull { name = "onCreate" - param(BundleClass) - }.ignored().hook().after { instance().cancel() } - method { + parameters(Bundle::class) + }?.hook()?.after { instance().cancel() } + firstMethodOrNull { name = "onStart" - emptyParam() - }.ignored().hook().after { instance().cancel() } + emptyParameters() + }?.hook()?.after { instance().cancel() } } /** 注入自定义错误对话框 */ - AppErrorsClass.apply { + AppErrorsClass.resolve().optional().apply { when { Build.VERSION.SDK_INT > Build.VERSION_CODES.R -> { - method { + firstMethodOrNull { name = "handleAppCrashLSPB" - paramCount = 6 - }.hook().after { + parameterCount = 6 + }?.hook()?.after { /** 如果为用户终止则不展示异常 */ if (args(index = 1).string() == "user-terminated") return@after /** 当前实例 */ - val context = appContext ?: field { name = "mContext" }.get(instance).cast() ?: return@after + val context = appContext ?: firstFieldOrNull { name = "mContext" }?.of(instance)?.get() ?: return@after /** 当前进程信息 */ val proc = args().first().any() ?: return@after YLog.warn("Received but got null ProcessRecord (Show UI failed)") @@ -441,29 +473,29 @@ object FrameworkHooker : YukiBaseHooker() { } } else -> { - method { + firstMethodOrNull { name = "handleShowAppErrorUi" - param(MessageClass) - }.hook().after { + parameters(Message::class) + }?.hook()?.after { /** 当前实例 */ - val context = appContext ?: field { name = "mContext" }.get(instance).cast() ?: return@after + val context = appContext ?: firstFieldOrNull { name = "mContext" }?.of(instance)?.get() ?: return@after /** 当前错误数据 */ val resultData = args().first().cast()?.obj /** 当前进程信息 */ - val proc = AppErrorDialog_DataClass.field { name = "proc" }.get(resultData).any() + val proc = AppErrorDialog_DataClass.resolve().optional().firstFieldOrNull { name = "proc" }?.of(resultData)?.get() /** 创建 APP 进程异常数据类 */ AppErrorsProcessData(instance, proc, resultData).handleShowAppErrorUi(context) } } } - method { + firstMethodOrNull { name = "handleAppCrashInActivityController" - returnType = BooleanType - }.hook().after { + returnType = Boolean::class + }?.hook()?.after { /** 当前实例 */ - val context = appContext ?: field { name = "mContext" }.get(instance).cast() ?: return@after + val context = appContext ?: firstFieldOrNull { name = "mContext" }?.of(instance)?.get() ?: return@after /** 当前进程信息 */ val proc = args().first().any() ?: return@after YLog.warn("Received but got null ProcessRecord") diff --git a/module-app/src/main/java/com/fankes/apperrorstracking/ui/activity/base/BaseActivity.kt b/module-app/src/main/java/com/fankes/apperrorstracking/ui/activity/base/BaseActivity.kt index 38fa000..c8ba488 100644 --- a/module-app/src/main/java/com/fankes/apperrorstracking/ui/activity/base/BaseActivity.kt +++ b/module-app/src/main/java/com/fankes/apperrorstracking/ui/activity/base/BaseActivity.kt @@ -19,14 +19,13 @@ * * This file is created by fankes on 2022/5/7. */ -@file:Suppress("DEPRECATION") - package com.fankes.apperrorstracking.ui.activity.base import android.app.ActivityManager import android.content.Intent import android.os.Build import android.os.Bundle +import android.view.LayoutInflater import androidx.appcompat.app.AppCompatActivity import androidx.core.content.res.ResourcesCompat import androidx.core.view.WindowCompat @@ -34,9 +33,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 com.highcapable.kavaref.KavaRef.Companion.resolve +import com.highcapable.kavaref.extension.genericSuperclassTypeArguments +import com.highcapable.kavaref.extension.toClassOrNull abstract class BaseActivity : AppCompatActivity() { @@ -45,10 +44,11 @@ abstract class BaseActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = current().generic()?.argument()?.method { + val bindingClass = javaClass.genericSuperclassTypeArguments().firstOrNull()?.toClassOrNull() + binding = bindingClass?.resolve()?.optional()?.firstMethodOrNull { name = "inflate" - param(LayoutInflaterClass) - }?.get()?.invoke(layoutInflater) ?: error("binding failed") + parameters(LayoutInflater::class) + }?.invoke(layoutInflater) ?: error("binding failed") if (Build.VERSION.SDK_INT >= 35) binding.root.fitsSystemWindows = true setContentView(binding.root) /** 隐藏系统的标题栏 */ @@ -58,6 +58,7 @@ abstract class BaseActivity : AppCompatActivity() { isAppearanceLightStatusBars = isNotSystemInDarkMode isAppearanceLightNavigationBars = isNotSystemInDarkMode } + @Suppress("DEPRECATION") ResourcesCompat.getColor(resources, R.color.colorThemeBackground, null).also { window?.statusBarColor = it window?.navigationBarColor = it diff --git a/module-app/src/main/java/com/fankes/apperrorstracking/utils/factory/BaseAdapterFactory.kt b/module-app/src/main/java/com/fankes/apperrorstracking/utils/factory/BaseAdapterFactory.kt index b5d437f..42c5e71 100644 --- a/module-app/src/main/java/com/fankes/apperrorstracking/utils/factory/BaseAdapterFactory.kt +++ b/module-app/src/main/java/com/fankes/apperrorstracking/utils/factory/BaseAdapterFactory.kt @@ -19,6 +19,8 @@ * * This file is created by fankes on 2022/6/3. */ +@file:Suppress("DEPRECATION") + package com.fankes.apperrorstracking.utils.factory import android.content.Context diff --git a/module-app/src/main/java/com/fankes/apperrorstracking/utils/factory/DialogBuilderFactory.kt b/module-app/src/main/java/com/fankes/apperrorstracking/utils/factory/DialogBuilderFactory.kt index f8037b9..61581b0 100644 --- a/module-app/src/main/java/com/fankes/apperrorstracking/utils/factory/DialogBuilderFactory.kt +++ b/module-app/src/main/java/com/fankes/apperrorstracking/utils/factory/DialogBuilderFactory.kt @@ -19,7 +19,7 @@ * * This file is created by fankes on 2022/5/12. */ -@file:Suppress("unused") +@file:Suppress("unused", "DEPRECATION") package com.fankes.apperrorstracking.utils.factory diff --git a/module-app/src/main/java/com/fankes/apperrorstracking/utils/factory/FunctionFactory.kt b/module-app/src/main/java/com/fankes/apperrorstracking/utils/factory/FunctionFactory.kt index 02a9704..5ea7876 100644 --- a/module-app/src/main/java/com/fankes/apperrorstracking/utils/factory/FunctionFactory.kt +++ b/module-app/src/main/java/com/fankes/apperrorstracking/utils/factory/FunctionFactory.kt @@ -19,7 +19,7 @@ * * This file is created by fankes on 2022/5/7. */ -@file:Suppress("unused", "NotificationPermission") +@file:Suppress("unused", "NotificationPermission", "DEPRECATION") package com.fankes.apperrorstracking.utils.factory diff --git a/module-app/src/main/res/layout/activity_main.xml b/module-app/src/main/res/layout/activity_main.xml index e24eca9..47ac1f0 100644 --- a/module-app/src/main/res/layout/activity_main.xml +++ b/module-app/src/main/res/layout/activity_main.xml @@ -776,6 +776,35 @@ android:textColor="@color/colorTextGray" android:textSize="11sp" /> + + + + + + + diff --git a/module-app/src/main/res/mipmap-xxhdpi/ic_kavaref.png b/module-app/src/main/res/mipmap-xxhdpi/ic_kavaref.png new file mode 100644 index 0000000..1fd5616 Binary files /dev/null and b/module-app/src/main/res/mipmap-xxhdpi/ic_kavaref.png differ diff --git a/module-app/src/main/res/values-ja/strings.xml b/module-app/src/main/res/values-ja/strings.xml index cbb7d79..627a136 100644 --- a/module-app/src/main/res/values-ja/strings.xml +++ b/module-app/src/main/res/values-ja/strings.xml @@ -157,4 +157,5 @@ システムビルド ID アプリのパッケージ名 その他の要件 + このモジュールは KavaRef を搭載しています。\n詳細はこちら https://github.com/HighCapable/KavaRef diff --git a/module-app/src/main/res/values-zh-rCN/strings.xml b/module-app/src/main/res/values-zh-rCN/strings.xml index f8502c8..ba2f9f8 100644 --- a/module-app/src/main/res/values-zh-rCN/strings.xml +++ b/module-app/src/main/res/values-zh-rCN/strings.xml @@ -159,4 +159,5 @@ 其它必要信息 以文件方式分享异常堆栈 使用文件的方式代替文本分享异常堆栈 + 此模块使用 KavaRef 强力驱动。\n了解更多 https://github.com/HighCapable/KavaRef \ No newline at end of file diff --git a/module-app/src/main/res/values-zh-rHK/strings.xml b/module-app/src/main/res/values-zh-rHK/strings.xml index eb066df..b4ec46e 100644 --- a/module-app/src/main/res/values-zh-rHK/strings.xml +++ b/module-app/src/main/res/values-zh-rHK/strings.xml @@ -155,4 +155,5 @@ 系統建置 ID 應用程式包名稱 其它必要資訊 + 此模組使用 KavaRef 強力驅動。\n了解更多 https://github.com/HighCapable/KavaRef \ No newline at end of file diff --git a/module-app/src/main/res/values-zh-rMO/strings.xml b/module-app/src/main/res/values-zh-rMO/strings.xml index b58b659..650c468 100644 --- a/module-app/src/main/res/values-zh-rMO/strings.xml +++ b/module-app/src/main/res/values-zh-rMO/strings.xml @@ -155,4 +155,5 @@ 系統建置 ID 應用程式包名稱 其它必要資訊 + 此模組使用 KavaRef 強力驅動。\n了解更多 https://github.com/HighCapable/KavaRef \ No newline at end of file diff --git a/module-app/src/main/res/values-zh-rTW/strings.xml b/module-app/src/main/res/values-zh-rTW/strings.xml index b0c3de3..549a57a 100644 --- a/module-app/src/main/res/values-zh-rTW/strings.xml +++ b/module-app/src/main/res/values-zh-rTW/strings.xml @@ -155,4 +155,5 @@ 系統建置 ID 應用程式包名稱 其它必要資訊 + 此模組使用 KavaRef 強力驅動。\n瞭解更多 https://github.com/HighCapable/KavaRef \ No newline at end of file diff --git a/module-app/src/main/res/values/strings.xml b/module-app/src/main/res/values/strings.xml index 0edadee..9ae8ace 100644 --- a/module-app/src/main/res/values/strings.xml +++ b/module-app/src/main/res/values/strings.xml @@ -162,4 +162,5 @@ Other Requirement Share errors stacktrace with file Share errors stacktrace using files instead of text + This Xposed Module is powered by KavaRef.\nLearn more https://github.com/HighCapable/KavaRef