diff --git a/app/src/main/java/com/fankes/tsbattery/hook/HookConst.kt b/app/src/main/java/com/fankes/tsbattery/const/ConstFactory.kt
similarity index 65%
rename from app/src/main/java/com/fankes/tsbattery/hook/HookConst.kt
rename to app/src/main/java/com/fankes/tsbattery/const/ConstFactory.kt
index 9a8bdc7..15b8786 100644
--- a/app/src/main/java/com/fankes/tsbattery/hook/HookConst.kt
+++ b/app/src/main/java/com/fankes/tsbattery/const/ConstFactory.kt
@@ -17,13 +17,30 @@
* and eula along with this software. If not, see
*
*
- * This file is Created by fankes on 2021/11/9.
+ * This file is Created by fankes on 2022/9/29.
*/
-package com.fankes.tsbattery.hook
+package com.fankes.tsbattery.const
-object HookConst {
+/**
+ * 包名常量定义类
+ */
+object PackageName {
- const val QQ_PACKAGE_NAME = "com.tencent.mobileqq"
- const val TIM_PACKAGE_NAME = "com.tencent.tim"
- const val WECHAT_PACKAGE_NAME = "com.tencent.mm"
+ /** QQ */
+ const val QQ = "com.tencent.mobileqq"
+
+ /** TIM */
+ const val TIM = "com.tencent.tim"
+
+ /** 微信 */
+ const val WECHAT = "com.tencent.mm"
+}
+
+/**
+ * 跳转常量定义类
+ */
+object JumpEvent {
+
+ /** 启动模块设置 */
+ const val OPEN_MODULE_SETTING = "tsbattery_open_module_settings"
}
\ No newline at end of file
diff --git a/app/src/main/java/com/fankes/tsbattery/data/ConfigData.kt b/app/src/main/java/com/fankes/tsbattery/data/ConfigData.kt
new file mode 100644
index 0000000..3985da7
--- /dev/null
+++ b/app/src/main/java/com/fankes/tsbattery/data/ConfigData.kt
@@ -0,0 +1,125 @@
+/*
+ * TSBattery - A new way to save your battery avoid cancer apps hacker it.
+ * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
+ * https://github.com/fankes/TSBattery
+ *
+ * This software is non-free but opensource software: you can redistribute it
+ * and/or modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * and eula along with this software. If not, see
+ *
+ *
+ * This file is Created by fankes on 2022/9/28.
+ */
+package com.fankes.tsbattery.data
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.widget.CompoundButton
+
+/**
+ * 全局配置存储控制类
+ */
+object ConfigData {
+
+ /** QQ、TIM 保守模式*/
+ const val ENABLE_QQ_TIM_PROTECT_MODE = "enable_qq_tim_protect_mode"
+
+ /** 自动关闭 QQ、TIM 的 CoreService */
+ const val ENABLE_KILL_QQ_TIM_CORESERVICE = "enable_kill_qq_tim_core_service"
+
+ /** 自动关闭 QQ、TIM 的 CoreService$KernelService */
+ const val ENABLE_KILLE_QQ_TIM_CORESERVICE_CHILD = "enable_kill_qq_tim_core_service_child"
+
+ /** 停用全部省电功能 (停用模块) */
+ const val DISABLE_ALL_HOOK = "disable_all_hook"
+
+ /** 当前的 [SharedPreferences] */
+ private var sharePrefs: SharedPreferences? = null
+
+ /**
+ * 读取 [SharedPreferences]
+ * @param key 键值名称
+ * @param value 键值内容
+ * @return [Boolean]
+ */
+ private fun getBoolean(key: String, value: Boolean = false) = sharePrefs?.getBoolean(key, value) ?: value
+
+ /**
+ * 存入 [SharedPreferences]
+ * @param key 键值名称
+ * @param value 键值内容
+ */
+ private fun putBoolean(key: String, value: Boolean = false) = sharePrefs?.edit()?.putBoolean(key, value)?.apply()
+
+ /**
+ * 初始化 [SharedPreferences]
+ * @param context 实例
+ */
+ fun init(context: Context) {
+ sharePrefs = context.getSharedPreferences("tsbattery_config", Context.MODE_PRIVATE)
+ }
+
+ /**
+ * 绑定到 [CompoundButton] 自动设置选中状态
+ * @param key 键值名称
+ * @param onChange 当改变时回调
+ */
+ fun CompoundButton.bind(key: String, onChange: (Boolean) -> Unit = {}) {
+ isChecked = getBoolean(key)
+ setOnCheckedChangeListener { button, isChecked ->
+ if (button.isPressed) {
+ putBoolean(key, isChecked)
+ onChange(isChecked)
+ }
+ }
+ }
+
+ /**
+ * 是否启用 QQ、TIM 保守模式
+ * @return [Boolean]
+ */
+ var isEnableQQTimProtectMode
+ get() = getBoolean(ENABLE_QQ_TIM_PROTECT_MODE)
+ set(value) {
+ putBoolean(ENABLE_QQ_TIM_PROTECT_MODE, value)
+ }
+
+ /**
+ * 是否启用自动关闭 QQ、TIM 的 CoreService
+ * @return [Boolean]
+ */
+ var isEnableKillQQTimCoreService
+ get() = getBoolean(ENABLE_KILL_QQ_TIM_CORESERVICE)
+ set(value) {
+ putBoolean(ENABLE_KILL_QQ_TIM_CORESERVICE, value)
+ }
+
+ /**
+ * 是否启用自动关闭 QQ、TIM 的 CoreService$KernelService
+ * @return [Boolean]
+ */
+ var isEnableKillQQTimCoreServiceChild
+ get() = getBoolean(ENABLE_KILLE_QQ_TIM_CORESERVICE_CHILD)
+ set(value) {
+ putBoolean(ENABLE_KILLE_QQ_TIM_CORESERVICE_CHILD, value)
+ }
+
+ /**
+ * 是否停用全部省电功能 (停用模块)
+ * @return [Boolean]
+ */
+ var isDisableAllHook
+ get() = getBoolean(DISABLE_ALL_HOOK)
+ set(value) {
+ putBoolean(DISABLE_ALL_HOOK, value)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/fankes/tsbattery/data/DataConst.kt b/app/src/main/java/com/fankes/tsbattery/data/DataConst.kt
deleted file mode 100644
index 40b07e8..0000000
--- a/app/src/main/java/com/fankes/tsbattery/data/DataConst.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * TSBattery - A new way to save your battery avoid cancer apps hacker it.
- * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
- * https://github.com/fankes/TSBattery
- *
- * This software is non-free but opensource software: you can redistribute it
- * and/or modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation; either
- * version 3 of the License, or any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * and eula along with this software. If not, see
- *
- *
- * This file is Created by fankes on 2022/3/28.
- */
-package com.fankes.tsbattery.data
-
-import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
-
-object DataConst {
-
- val ENABLE_RUN_INFO = PrefsData("_tip_run_info", false)
- val ENABLE_NOTIFY_TIP = PrefsData("_tip_in_notify", true)
- val ENABLE_SETTING_TIP = PrefsData("_tip_in_setting", true)
- val ENABLE_QQTIM_WHITE_MODE = PrefsData("_qqtim_white_mode", false)
- val ENABLE_QQTIM_CORESERVICE_BAN = PrefsData("_qqtim_core_service_ban", false)
- val ENABLE_QQTIM_CORESERVICE_CHILD_BAN = PrefsData("_qqtim_core_service_child_ban", false)
- val DISABLE_WECHAT_HOOK = PrefsData("_disable_wechat_hook", false)
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/fankes/tsbattery/hook/HookEntry.kt b/app/src/main/java/com/fankes/tsbattery/hook/HookEntry.kt
index 41cd25f..84a26cc 100644
--- a/app/src/main/java/com/fankes/tsbattery/hook/HookEntry.kt
+++ b/app/src/main/java/com/fankes/tsbattery/hook/HookEntry.kt
@@ -23,36 +23,16 @@
package com.fankes.tsbattery.hook
-import android.app.Activity
-import android.app.Service
-import android.content.ComponentName
-import android.content.Intent
-import android.os.Build
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import android.view.ViewGroup.MarginLayoutParams
-import android.widget.Toast
-import androidx.core.app.ServiceCompat
-import com.fankes.tsbattery.BuildConfig
-import com.fankes.tsbattery.data.DataConst
-import com.fankes.tsbattery.hook.HookConst.QQ_PACKAGE_NAME
-import com.fankes.tsbattery.hook.HookConst.TIM_PACKAGE_NAME
-import com.fankes.tsbattery.hook.HookConst.WECHAT_PACKAGE_NAME
-import com.fankes.tsbattery.ui.activity.MainActivity
-import com.fankes.tsbattery.utils.factory.dp
-import com.fankes.tsbattery.utils.factory.showDialog
-import com.fankes.tsbattery.utils.factory.versionCode
+import com.fankes.tsbattery.const.PackageName
+import com.fankes.tsbattery.data.ConfigData
+import com.fankes.tsbattery.hook.entity.QQTIMHooker
+import com.fankes.tsbattery.hook.entity.WeChatHooker
import com.fankes.tsbattery.utils.factory.versionName
import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed
-import com.highcapable.yukihookapi.hook.bean.VariousClass
-import com.highcapable.yukihookapi.hook.factory.*
-import com.highcapable.yukihookapi.hook.log.loggerD
-import com.highcapable.yukihookapi.hook.log.loggerE
+import com.highcapable.yukihookapi.hook.factory.configs
+import com.highcapable.yukihookapi.hook.factory.encase
+import com.highcapable.yukihookapi.hook.factory.registerModuleAppActivities
import com.highcapable.yukihookapi.hook.param.PackageParam
-import com.highcapable.yukihookapi.hook.type.android.*
-import com.highcapable.yukihookapi.hook.type.java.*
import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit
@InjectYukiHookWithXposed(isUsingResourcesHook = false)
@@ -60,662 +40,41 @@ class HookEntry : IYukiHookXposedInit {
companion object {
- /** QQ、TIM 存在的类 */
- private const val SplashActivityClass = "$QQ_PACKAGE_NAME.activity.SplashActivity"
-
- /** QQ、TIM 存在的类 */
- private const val CoreServiceClass = "$QQ_PACKAGE_NAME.app.CoreService"
-
- /** QQ、TIM 存在的类 */
- private const val CoreService_KernelServiceClass = "$QQ_PACKAGE_NAME.app.CoreService\$KernelService"
-
- /** QQ、TIM 新版本存在的类 */
- private const val FormSimpleItemClass = "$QQ_PACKAGE_NAME.widget.FormSimpleItem"
-
- /** QQ、TIM 旧版本存在的类 */
- private const val FormCommonSingleLineItemClass = "$QQ_PACKAGE_NAME.widget.FormCommonSingleLineItem"
-
- /** 微信存在的类 */
- private const val LauncherUIClass = "$WECHAT_PACKAGE_NAME.ui.LauncherUI"
-
- /** 根据多个版本存的不同的类 */
- private val BaseChatPieClass = VariousClass(
- "$QQ_PACKAGE_NAME.activity.aio.core.BaseChatPie",
- "$QQ_PACKAGE_NAME.activity.BaseChatPie"
- )
+ /** 是否完全支持当前版本 */
+ var isHookClientSupport = true
}
- /** 是否完全支持当前版本 */
- private var isHookClientSupport = true
-
- /**
- * 这个类 QQ 的 BaseChatPie 是控制聊天界面的
- *
- * 里面有两个随机混淆的方法 ⬇
- *
- * remainScreenOn、cancelRemainScreenOn
- *
- * 这两个方法一个是挂起电源锁常驻亮屏
- *
- * 一个是停止常驻亮屏
- *
- * 不由分说每个版本混淆的方法名都会变
- *
- * 所以说每个版本重新适配 - 也可以提交分支帮我适配
- *
- * - ❗Hook 错了方法会造成闪退!
- * @param version QQ 版本
- */
- private fun PackageParam.hookQQBaseChatPie(version: String) {
- when (version) {
- "8.0.0" -> {
- interceptBaseChatPie(methodName = "bq")
- interceptBaseChatPie(methodName = "aL")
- }
- "8.0.5", "8.0.7" -> {
- interceptBaseChatPie(methodName = "bw")
- interceptBaseChatPie(methodName = "aQ")
- }
- "8.1.0", "8.1.3" -> {
- interceptBaseChatPie(methodName = "bE")
- interceptBaseChatPie(methodName = "aT")
- }
- "8.1.5" -> {
- interceptBaseChatPie(methodName = "bF")
- interceptBaseChatPie(methodName = "aT")
- }
- "8.1.8", "8.2.0", "8.2.6" -> {
- interceptBaseChatPie(methodName = "bC")
- interceptBaseChatPie(methodName = "aT")
- }
- "8.2.7", "8.2.8", "8.2.11", "8.3.0" -> {
- interceptBaseChatPie(methodName = "bE")
- interceptBaseChatPie(methodName = "aV")
- }
- "8.3.5" -> {
- interceptBaseChatPie(methodName = "bR")
- interceptBaseChatPie(methodName = "aX")
- }
- "8.3.6" -> {
- interceptBaseChatPie(methodName = "cp")
- interceptBaseChatPie(methodName = "aX")
- }
- "8.3.9" -> {
- interceptBaseChatPie(methodName = "cj")
- interceptBaseChatPie(methodName = "aT")
- }
- "8.4.1", "8.4.5" -> {
- interceptBaseChatPie(methodName = "ck")
- interceptBaseChatPie(methodName = "aT")
- }
- "8.4.8", "8.4.10", "8.4.17", "8.4.18", "8.5.0" -> {
- interceptBaseChatPie(methodName = "remainScreenOn")
- interceptBaseChatPie(methodName = "cancelRemainScreenOn")
- }
- "8.5.5" -> {
- interceptBaseChatPie(methodName = "bT")
- interceptBaseChatPie(methodName = "aN")
- }
- "8.6.0", "8.6.5", "8.7.0", "8.7.5", "8.7.8", "8.8.0", "8.8.3", "8.8.5" -> {
- interceptBaseChatPie(methodName = "ag")
- interceptBaseChatPie(methodName = "ah")
- }
- "8.8.11", "8.8.12" -> {
- interceptBaseChatPie(methodName = "bc")
- interceptBaseChatPie(methodName = "bd")
- }
- "8.8.17", "8.8.20" -> {
- interceptBaseChatPie(methodName = "bd")
- interceptBaseChatPie(methodName = "be")
- }
- "8.8.23", "8.8.28" -> {
- interceptBaseChatPie(methodName = "bf")
- interceptBaseChatPie(methodName = "bg")
- }
- "8.8.33" -> {
- interceptBaseChatPie(methodName = "bg")
- interceptBaseChatPie(methodName = "bh")
- }
- "8.8.35", "8.8.38" -> {
- interceptBaseChatPie(methodName = "bi")
- interceptBaseChatPie(methodName = "bj")
- }
- "8.8.50" -> {
- interceptBaseChatPie(methodName = "bj")
- interceptBaseChatPie(methodName = "bk")
- }
- "8.8.55", "8.8.68", "8.8.80" -> {
- interceptBaseChatPie(methodName = "bk")
- interceptBaseChatPie(methodName = "bl")
- }
- "8.8.83", "8.8.85", "8.8.88", "8.8.90" -> {
- interceptBaseChatPie(methodName = "bl")
- interceptBaseChatPie(methodName = "bm")
- }
- "8.8.93", "8.8.95" -> {
- interceptBaseChatPie(methodName = "J3")
- interceptBaseChatPie(methodName = "S")
- }
- "8.8.98" -> {
- interceptBaseChatPie(methodName = "M3")
- interceptBaseChatPie(methodName = "S")
- }
- "8.9.0", "8.9.1", "8.9.2" -> {
- interceptBaseChatPie(methodName = "N3")
- interceptBaseChatPie(methodName = "S")
- }
- "8.9.3", "8.9.5" -> {
- interceptBaseChatPie(methodName = "H3")
- interceptBaseChatPie(methodName = "P")
- }
- "8.9.8", "8.9.10" -> {
- interceptBaseChatPie(methodName = "H3")
- interceptBaseChatPie(methodName = "N")
- }
- else -> {
- isHookClientSupport = false
- loggerD(msg = "$version not supported!")
- }
- }
- }
-
- /**
- * 拦截 [BaseChatPieClass] 的目标方法体封装
- * @param methodName 方法名
- */
- private fun PackageParam.interceptBaseChatPie(methodName: String) =
- BaseChatPieClass.hook {
- injectMember {
- method {
- name = methodName
- emptyParam()
- returnType = UnitType
- }
- intercept()
- }
- }
-
- /** Hook 系统电源锁 */
- private fun PackageParam.hookSystemWakeLock() =
- PowerManager_WakeLockClass.hook {
- injectMember {
- method {
- name = "acquireLocked"
- emptyParam()
- }
- intercept()
- }
- }
-
- /** 增加通知栏文本显示守护状态 */
- private fun PackageParam.hookNotification() =
- Notification_BuilderClass.hook {
- injectMember {
- method {
- name = "setContentText"
- param(CharSequenceType)
- }
- beforeHook {
- if (prefs.get(DataConst.ENABLE_NOTIFY_TIP))
- when (args().first().cast()) {
- "QQ正在后台运行" ->
- args().first().set("QQ正在后台运行 - TSBattery 守护中")
- "TIM正在后台运行" ->
- args().first().set("TIM正在后台运行 - TSBattery 守护中")
- }
- }
- }
- }
-
- /**
- * 提示模块运行信息 QQ、TIM、微信
- * @param isQQ 是否为 QQ
- * @param isTIM 是否为 TIM
- */
- private fun PackageParam.hookModuleRunningInfo(isQQ: Boolean = false, isTIM: Boolean = false) =
- when {
- isQQ || isTIM -> SplashActivityClass.hook {
- /**
- * Hook 启动界面的第一个 [Activity]
- * QQ 和 TIM 都是一样的类
- * 在里面加入提示运行信息的对话框测试模块是否激活
- */
- injectMember {
- method {
- name = "doOnCreate"
- param(BundleClass)
- }
- afterHook {
- if (prefs.get(DataConst.ENABLE_RUN_INFO))
- instance().apply {
- showDialog {
- title = "TSBattery 已激活"
- msg = "[提示模块运行信息功能已打开]\n\n" +
- (if (isQQ && isHookClientSupport.not())
- "❎ 当前版本 $versionName($versionCode) 不在兼容列表,请自行测试是否生效~\n\n"
- else "✅ 模块工作看起来一切正常,请自行测试是否能达到省电效果。\n\n") +
- "已生效模块版本:${BuildConfig.VERSION_NAME}\n" +
- "当前模式:${if (prefs.get(DataConst.ENABLE_QQTIM_WHITE_MODE)) "保守模式" else "完全模式"}" +
- "\n\n包名:${packageName}\n版本:$versionName($versionCode)" +
- "\n\n模块只对挂后台锁屏情况下有省电效果," +
- "请不要将过多的群提醒,消息通知打开,这样子在使用过程时照样会极其耗电。\n\n" +
- "如果你不想看到此提示。请在模块设置中关闭“提示模块运行信息”,此设置默认关闭。\n\n" +
- "持续常驻使用 QQ、TIM 依然会耗电,任何软件都是如此," +
- "模块是无法帮你做到前台不耗电的。\n\n" +
- "开发者 酷安 @星夜不荟\n未经允许禁止修改或复制我的劳动成果。"
- confirmButton(text = "我知道了")
- noCancelable()
- }
- }
- }
- }
- }
- else -> LauncherUIClass.hook {
- /**
- * Hook 启动界面的第一个 [Activity]
- * 在里面加入提示运行信息的对话框测试模块是否激活
- */
- injectMember {
- method {
- name = "onCreate"
- param(BundleClass)
- }
- afterHook {
- if (prefs.get(DataConst.ENABLE_RUN_INFO))
- instance().apply {
- showDialog {
- title = "TSBattery 已激活"
- msg = "[提示模块运行信息功能已打开]\n\n" +
- "模块工作看起来一切正常,请自行测试是否能达到省电效果。\n\n" +
- "已生效模块版本:${BuildConfig.VERSION_NAME}\n" +
- "当前模式:基础省电" +
- "\n\n包名:${packageName}\n版本:$versionName($versionCode)" +
- "\n\n当前只支持微信的基础省电,即系统电源锁,后续会继续适配微信相关的省电功能(在新建文件夹了)。\n\n" +
- "如果你不想看到此提示。请在模块设置中关闭“提示模块运行信息”,此设置默认关闭。\n\n" +
- "持续常驻使用微信依然会耗电,任何软件都是如此," +
- "模块是无法帮你做到前台不耗电的。\n\n" +
- "开发者 酷安 @星夜不荟\n未经允许禁止修改或复制我的劳动成果。"
- confirmButton(text = "我知道了") { finish() }
- noCancelable()
- }
- }
- }
- }
- }
- }
-
- /**
- * Hook CoreService QQ、TIM
- * @param isQQ 是否为 QQ - 单独处理
- */
- private fun PackageParam.hookCoreService(isQQ: Boolean) {
- CoreServiceClass.hook {
- if (isQQ) {
- injectMember {
- method { name = "startTempService" }
- intercept()
- }.ignoredNoSuchMemberFailure()
- injectMember {
- method {
- name = "startCoreService"
- param(BooleanType)
- }
- intercept()
- }.ignoredNoSuchMemberFailure()
- injectMember {
- method {
- name = "onStartCommand"
- param(IntentClass, IntType, IntType)
- }
- replaceTo(any = 2)
- }.ignoredNoSuchMemberFailure()
- }
- injectMember {
- method { name = "onCreate" }
- afterHook {
- if (prefs.get(DataConst.ENABLE_QQTIM_CORESERVICE_BAN))
- instance().apply {
- ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
- stopSelf()
- loggerD(msg = "Shutdown CoreService OK!")
- }
- }
- }
- }
- CoreService_KernelServiceClass.hook {
- injectMember {
- method { name = "onCreate" }
- afterHook {
- if (prefs.get(DataConst.ENABLE_QQTIM_CORESERVICE_CHILD_BAN))
- instance().apply {
- ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
- stopSelf()
- loggerD(msg = "Shutdown CoreService\$KernelService OK!")
- }
- }
- }
- injectMember {
- method {
- name = "onStartCommand"
- param(IntentClass, IntType, IntType)
- }
- replaceTo(any = 2)
- }.ignoredNoSuchMemberFailure()
- }
- }
-
- /**
- * 将激活状态插入到设置页面
- * @param isQQ 是否为 QQ - 单独处理
- */
- private fun PackageParam.hookQQSettingsSettingActivity(isQQ: Boolean) =
- findClass(name = "$QQ_PACKAGE_NAME.activity.QQSettingSettingActivity").hook {
- injectMember {
- method {
- name = "doOnCreate"
- param(BundleClass)
- afterHook {
- /** 是否启用 Hook */
- if (prefs.get(DataConst.ENABLE_SETTING_TIP).not()) return@afterHook
- /** 当前的顶级 Item 实例 */
- val formItemRefRoot = field {
- type(FormSimpleItemClass).index(num = 1)
- }.ignored().get(instance).cast() ?: field {
- type(FormCommonSingleLineItemClass).index(num = 1)
- }.ignored().get(instance).cast()
- /** 创建一个新的 Item */
- FormSimpleItemClass.toClass().buildOf(instance) { param(ContextClass) }?.current {
- method {
- name = "setLeftText"
- param(CharSequenceType)
- }.call("TSBattery")
- method {
- name = "setRightText"
- param(CharSequenceType)
- }.call("${if (isQQ && isHookClientSupport.not()) "❎" else "✅"} ${BuildConfig.VERSION_NAME}")
- method {
- name = "setBgType"
- param(IntType)
- }.call(if (isQQ) 0 else 2)
- }?.apply {
- setOnClickListener {
- instance().apply {
- showDialog {
- title = "TSBattery 守护中"
- msg = (if (isQQ && isHookClientSupport.not())
- "❎ 当前版本 $versionName($versionCode) 不在兼容列表,请自行测试是否生效~\n\n" else "") +
- "已生效模块版本:${BuildConfig.VERSION_NAME}\n" +
- "当前模式:${if (prefs.get(DataConst.ENABLE_QQTIM_WHITE_MODE)) "保守模式" else "完全模式"}" +
- "\n\n包名:${packageName}\n版本:$versionName($versionCode)" +
- "\n\n模块只对挂后台锁屏情况下有省电效果," +
- "请不要将过多的群提醒,消息通知打开,这样子在使用过程时照样会极其耗电。\n\n" +
- "持续常驻使用 QQ、TIM 依然会耗电,任何软件都是如此," +
- "模块是无法帮你做到前台不耗电的。\n\n" +
- "开发者 酷安 @星夜不荟\n未经允许禁止修改或复制我的劳动成果。"
- confirmButton(text = "打开模块设置") {
- runCatching {
- startActivity(Intent().apply {
- flags = Intent.FLAG_ACTIVITY_NEW_TASK
- component = ComponentName(
- BuildConfig.APPLICATION_ID,
- MainActivity::class.java.name
- )
- })
- }.onFailure { Toast.makeText(context, "启动失败", Toast.LENGTH_SHORT).show() }
- }
- cancelButton(text = "关闭")
- }
- }
- }
- }?.also { item ->
- var listGroup = formItemRefRoot?.parent as? ViewGroup?
- val lparam = (if (listGroup?.childCount == 1) {
- listGroup = listGroup.parent as? ViewGroup
- (formItemRefRoot?.parent as? View?)?.layoutParams
- } else formItemRefRoot?.layoutParams) ?: ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
- /** 设置圆角和间距 */
- if (isQQ) (lparam as? MarginLayoutParams?)?.setMargins(0, 15.dp(item.context), 0, 0)
- /** 将 Item 添加到设置界面 */
- listGroup?.also { if (isQQ) it.addView(item, lparam) else it.addView(item, 0, lparam) }
- }
- }
- }
- }
- }
-
override fun onInit() = configs {
debugLog { tag = "TSBattery" }
isDebug = false
- isEnableModulePrefsCache = false
isEnableDataChannel = false
}
+ /**
+ * 装载对象是否为 QQ、TIM、微信
+ * @return [Boolean]
+ */
+ private fun PackageParam.isCurrentScope() = packageName.let { it == PackageName.QQ || it == PackageName.TIM || it == PackageName.WECHAT }
+
override fun onHook() = encase {
- loadApp(QQ_PACKAGE_NAME) {
- hookSystemWakeLock()
- hookNotification()
- hookCoreService(isQQ = true)
- hookModuleRunningInfo(isQQ = true)
- hookQQSettingsSettingActivity(isQQ = true)
- if (prefs.get(DataConst.ENABLE_QQTIM_WHITE_MODE)) return@loadApp
- /** 通过在生命周期里取到应用的版本号 */
- onAppLifecycle { onCreate { hookQQBaseChatPie(versionName) } }
- /**
- * 干掉消息收发功能的电源锁
- * 每个版本的差异暂未做排查
- * 旧版本理论上没有这个类
- */
- findClass(name = "$QQ_PACKAGE_NAME.msf.service.y").hook {
- injectMember {
- method {
- name = "a"
- param(StringType, LongType)
- returnType = UnitType
+ loadApp {
+ if (isCurrentScope()) onAppLifecycle {
+ attachBaseContext { baseContext, hasCalledSuper ->
+ if (hasCalledSuper) return@attachBaseContext
+ ConfigData.init(baseContext)
+ when (baseContext.packageName) {
+ PackageName.QQ, PackageName.TIM -> loadHooker(QQTIMHooker.apply { appVersionName = baseContext.versionName })
+ PackageName.WECHAT -> loadHooker(WeChatHooker)
}
- intercept()
- }.onAllFailure { loggerE(msg = "Hook MsfService Failed $it") }
- }.ignoredHookClassNotFoundFailure()
- /**
- * 干掉自动上传服务的电源锁
- * 每个版本的差异暂未做排查
- */
- findClass(name = "com.tencent.upload.impl.UploadServiceImpl").hook {
- injectMember {
- method { name = "acquireWakeLockIfNot" }
- intercept()
- }.onAllFailure { loggerE(msg = "Hook UploadServiceImpl Failed $it") }
- }.ignoredHookClassNotFoundFailure()
- /**
- * Hook 掉一个一像素保活 [Activity] 真的我怎么都想不到讯哥的程序员做出这种事情
- * 这个东西经过测试会在锁屏的时候吊起来,解锁的时候自动 finish(),无限耍流氓耗电
- * 2022/1/25 后期查证:锁屏界面消息快速回复窗口的解锁后拉起保活界面,也是毒瘤
- */
- findClass(name = "$QQ_PACKAGE_NAME.activity.QQLSUnlockActivity").hook {
- injectMember {
- method {
- name = "onCreate"
- param(BundleClass)
- }
- var origDevice = ""
- beforeHook {
- /** 由于在 onCreate 里有一行判断只要型号是 xiaomi 的设备就开电源锁,所以说这里临时替换成菊花厂 */
- origDevice = Build.MANUFACTURER
- if (Build.MANUFACTURER.lowercase() == "xiaomi")
- BuildClass.field { name = "MANUFACTURER" }.get().set("HUAWEI")
- }
- afterHook {
- instance().finish()
- /** 这里再把型号替换回去 - 不影响应用变量等 Xposed 模块修改的型号 */
- BuildClass.field { name = "MANUFACTURER" }.get().set(origDevice)
+ }
+ onCreate {
+ when (packageName) {
+ PackageName.QQ, PackageName.TIM ->
+ registerModuleAppActivities(proxy = "${PackageName.QQ}.activity.QQSettingSettingActivity")
+ PackageName.WECHAT -> registerModuleAppActivities(proxy = "${PackageName.WECHAT}.plugin.welab.ui.WelabMainUI")
}
}
}
- /**
- * 这个东西同上
- * 反正也是一个一像素保活的 [Activity]
- * 讯哥的程序员真的有你的
- * 2022/1/25 后期查证:锁屏界面消息快速回复窗口
- */
- findClass("$QQ_PACKAGE_NAME.activity.QQLSActivity\$14", "ktq").hook {
- injectMember {
- method { name = "run" }
- intercept()
- }.ignoredAllFailure()
- }.ignoredHookClassNotFoundFailure()
- /**
- * 这个是毒瘤核心类
- * WakeLockMonitor
- * 这个名字真的起的特别诗情画意
- * 带给用户的却是 shit 一样的体验
- * 里面有各种使用 Handler 和 Timer 的各种耗时常驻后台耗电办法持续接收消息
- * 直接循环全部方法全部干掉
- * 👮🏻 经过排查 Play 版本没这个类...... Emmmm 不想说啥了
- */
- findClass(name = "com.tencent.qapmsdk.qqbattery.monitor.WakeLockMonitor").hook {
- injectMember {
- method {
- name = "onHook"
- param(StringType, AnyType, AnyArrayClass, AnyType)
- }
- intercept()
- }
- injectMember {
- method {
- name = "doReport"
- param("com.tencent.qapmsdk.qqbattery.monitor.WakeLockMonitor\$WakeLockEntity", IntType)
- }
- intercept()
- }
- injectMember {
- method {
- name = "afterHookedMethod"
- param("com.tencent.qapmsdk.qqbattery.monitor.MethodHookParam")
- }
- intercept()
- }
- injectMember {
- method {
- name = "beforeHookedMethod"
- param("com.tencent.qapmsdk.qqbattery.monitor.MethodHookParam")
- }
- intercept()
- }
- injectMember {
- method { name = "onAppBackground" }
- intercept()
- }
- injectMember {
- method {
- name = "onOtherProcReport"
- param(BundleClass)
- }
- intercept()
- }
- injectMember {
- method { name = "onProcessRun30Min" }
- intercept()
- }
- injectMember {
- method { name = "onProcessBG5Min" }
- intercept()
- }
- injectMember {
- method {
- name = "writeReport"
- param(BooleanType)
- }
- intercept()
- }
- }.ignoredHookClassNotFoundFailure()
- /**
- * 这个是毒瘤核心操作类
- * 功能同上、全部拦截
- * 👮🏻 经过排查 Play 版本也没这个类...... Emmmm 不想说啥了
- */
- findClass(name = "com.tencent.qapmsdk.qqbattery.QQBatteryMonitor").hook {
- injectMember {
- method { name = "start" }
- intercept()
- }
- injectMember {
- method { name = "stop" }
- intercept()
- }
- injectMember {
- method {
- name = "handleMessage"
- param(MessageClass)
- }
- replaceToTrue()
- }
- injectMember {
- method { name = "startMonitorInner" }
- intercept()
- }
- injectMember {
- method { name = "onAppBackground" }
- intercept()
- }
- injectMember {
- method { name = "onAppForeground" }
- intercept()
- }
- injectMember {
- method {
- name = "setLogWhite"
- paramCount = 2
- }
- intercept()
- }
- injectMember {
- method {
- name = "setCmdWhite"
- paramCount = 2
- }
- intercept()
- }
- injectMember {
- method {
- name = "onWriteLog"
- param(StringType, StringType)
- }
- intercept()
- }
- injectMember {
- method {
- name = "onCmdRequest"
- param(StringType)
- }
- intercept()
- }
- injectMember {
- method {
- name = "addData"
- paramCount = 4
- }
- intercept()
- }
- injectMember {
- method {
- name = "onGpsScan"
- paramCount = 2
- }
- intercept()
- }
- }.ignoredHookClassNotFoundFailure()
- }
- loadApp(TIM_PACKAGE_NAME) {
- hookSystemWakeLock()
- hookNotification()
- hookCoreService(isQQ = false)
- hookModuleRunningInfo(isTIM = true)
- hookQQSettingsSettingActivity(isQQ = false)
- }
- loadApp(WECHAT_PACKAGE_NAME) {
- if (prefs.get(DataConst.DISABLE_WECHAT_HOOK)) return@loadApp
- hookSystemWakeLock()
- hookModuleRunningInfo()
- loggerD(msg = "ウイチャット:それが機能するかどうかはわかりませんでした")
}
}
}
diff --git a/app/src/main/java/com/fankes/tsbattery/hook/entity/QQTIMHooker.kt b/app/src/main/java/com/fankes/tsbattery/hook/entity/QQTIMHooker.kt
new file mode 100644
index 0000000..bcc3cc4
--- /dev/null
+++ b/app/src/main/java/com/fankes/tsbattery/hook/entity/QQTIMHooker.kt
@@ -0,0 +1,562 @@
+/*
+ * TSBattery - A new way to save your battery avoid cancer apps hacker it.
+ * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
+ * https://github.com/fankes/TSBattery
+ *
+ * This software is non-free but opensource software: you can redistribute it
+ * and/or modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * and eula along with this software. If not, see
+ *
+ *
+ * This file is Created by fankes on 2022/9/29.
+ */
+package com.fankes.tsbattery.hook.entity
+
+import android.app.Activity
+import android.app.Service
+import android.os.Build
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.app.ServiceCompat
+import com.fankes.tsbattery.BuildConfig
+import com.fankes.tsbattery.const.PackageName
+import com.fankes.tsbattery.data.ConfigData
+import com.fankes.tsbattery.hook.HookEntry
+import com.fankes.tsbattery.hook.factory.hookSystemWakeLock
+import com.fankes.tsbattery.hook.factory.jumpToModuleSettings
+import com.fankes.tsbattery.hook.factory.startModuleSettings
+import com.fankes.tsbattery.utils.factory.dp
+import com.highcapable.yukihookapi.hook.bean.VariousClass
+import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
+import com.highcapable.yukihookapi.hook.factory.buildOf
+import com.highcapable.yukihookapi.hook.factory.current
+import com.highcapable.yukihookapi.hook.factory.field
+import com.highcapable.yukihookapi.hook.log.loggerD
+import com.highcapable.yukihookapi.hook.log.loggerE
+import com.highcapable.yukihookapi.hook.type.android.*
+import com.highcapable.yukihookapi.hook.type.java.*
+
+/**
+ * Hook QQ、TIM
+ */
+object QQTIMHooker : YukiBaseHooker() {
+
+ /** QQ、TIM 存在的类 */
+ const val JumpActivityClass = "${PackageName.QQ}.activity.JumpActivity"
+
+ /** QQ、TIM 存在的类 */
+ private const val QQSettingSettingActivityClass = "${PackageName.QQ}.activity.QQSettingSettingActivity"
+
+ /** QQ、TIM 新版本存在的类 */
+ private const val FormSimpleItemClass = "${PackageName.QQ}.widget.FormSimpleItem"
+
+ /** QQ、TIM 旧版本存在的类 */
+ private const val FormCommonSingleLineItemClass = "${PackageName.QQ}.widget.FormCommonSingleLineItem"
+
+ /** QQ、TIM 存在的类 */
+ private const val CoreServiceClass = "${PackageName.QQ}.app.CoreService"
+
+ /** QQ、TIM 存在的类 */
+ private const val CoreService_KernelServiceClass = "${PackageName.QQ}.app.CoreService\$KernelService"
+
+ /** 根据多个版本存的不同的类 */
+ private val BaseChatPieClass =
+ VariousClass("${PackageName.QQ}.activity.aio.core.BaseChatPie", "${PackageName.QQ}.activity.BaseChatPie")
+
+ /**
+ * 当前是否为 QQ
+ * @return [Boolean]
+ */
+ private val isQQ get() = packageName == PackageName.QQ
+
+ /** 当前宿主的版本 */
+ var appVersionName = ""
+
+ /**
+ * 这个类 QQ 的 BaseChatPie 是控制聊天界面的
+ *
+ * 里面有两个随机混淆的方法 ⬇
+ *
+ * remainScreenOn、cancelRemainScreenOn
+ *
+ * 这两个方法一个是挂起电源锁常驻亮屏
+ *
+ * 一个是停止常驻亮屏
+ *
+ * 不由分说每个版本混淆的方法名都会变
+ *
+ * 所以说每个版本重新适配 - 也可以提交分支帮我适配
+ *
+ * - ❗Hook 错了方法会造成闪退!
+ */
+ private fun hookQQBaseChatPie() {
+ if (isQQ) when (appVersionName) {
+ "8.0.0" -> {
+ hookBaseChatPie(methodName = "bq")
+ hookBaseChatPie(methodName = "aL")
+ }
+ "8.0.5", "8.0.7" -> {
+ hookBaseChatPie(methodName = "bw")
+ hookBaseChatPie(methodName = "aQ")
+ }
+ "8.1.0", "8.1.3" -> {
+ hookBaseChatPie(methodName = "bE")
+ hookBaseChatPie(methodName = "aT")
+ }
+ "8.1.5" -> {
+ hookBaseChatPie(methodName = "bF")
+ hookBaseChatPie(methodName = "aT")
+ }
+ "8.1.8", "8.2.0", "8.2.6" -> {
+ hookBaseChatPie(methodName = "bC")
+ hookBaseChatPie(methodName = "aT")
+ }
+ "8.2.7", "8.2.8", "8.2.11", "8.3.0" -> {
+ hookBaseChatPie(methodName = "bE")
+ hookBaseChatPie(methodName = "aV")
+ }
+ "8.3.5" -> {
+ hookBaseChatPie(methodName = "bR")
+ hookBaseChatPie(methodName = "aX")
+ }
+ "8.3.6" -> {
+ hookBaseChatPie(methodName = "cp")
+ hookBaseChatPie(methodName = "aX")
+ }
+ "8.3.9" -> {
+ hookBaseChatPie(methodName = "cj")
+ hookBaseChatPie(methodName = "aT")
+ }
+ "8.4.1", "8.4.5" -> {
+ hookBaseChatPie(methodName = "ck")
+ hookBaseChatPie(methodName = "aT")
+ }
+ "8.4.8", "8.4.10", "8.4.17", "8.4.18", "8.5.0" -> {
+ hookBaseChatPie(methodName = "remainScreenOn")
+ hookBaseChatPie(methodName = "cancelRemainScreenOn")
+ }
+ "8.5.5" -> {
+ hookBaseChatPie(methodName = "bT")
+ hookBaseChatPie(methodName = "aN")
+ }
+ "8.6.0", "8.6.5", "8.7.0", "8.7.5", "8.7.8", "8.8.0", "8.8.3", "8.8.5" -> {
+ hookBaseChatPie(methodName = "ag")
+ hookBaseChatPie(methodName = "ah")
+ }
+ "8.8.11", "8.8.12" -> {
+ hookBaseChatPie(methodName = "bc")
+ hookBaseChatPie(methodName = "bd")
+ }
+ "8.8.17", "8.8.20" -> {
+ hookBaseChatPie(methodName = "bd")
+ hookBaseChatPie(methodName = "be")
+ }
+ "8.8.23", "8.8.28" -> {
+ hookBaseChatPie(methodName = "bf")
+ hookBaseChatPie(methodName = "bg")
+ }
+ "8.8.33" -> {
+ hookBaseChatPie(methodName = "bg")
+ hookBaseChatPie(methodName = "bh")
+ }
+ "8.8.35", "8.8.38" -> {
+ hookBaseChatPie(methodName = "bi")
+ hookBaseChatPie(methodName = "bj")
+ }
+ "8.8.50" -> {
+ hookBaseChatPie(methodName = "bj")
+ hookBaseChatPie(methodName = "bk")
+ }
+ "8.8.55", "8.8.68", "8.8.80" -> {
+ hookBaseChatPie(methodName = "bk")
+ hookBaseChatPie(methodName = "bl")
+ }
+ "8.8.83", "8.8.85", "8.8.88", "8.8.90" -> {
+ hookBaseChatPie(methodName = "bl")
+ hookBaseChatPie(methodName = "bm")
+ }
+ "8.8.93", "8.8.95" -> {
+ hookBaseChatPie(methodName = "J3")
+ hookBaseChatPie(methodName = "S")
+ }
+ "8.8.98" -> {
+ hookBaseChatPie(methodName = "M3")
+ hookBaseChatPie(methodName = "S")
+ }
+ "8.9.0", "8.9.1", "8.9.2" -> {
+ hookBaseChatPie(methodName = "N3")
+ hookBaseChatPie(methodName = "S")
+ }
+ "8.9.3", "8.9.5" -> {
+ hookBaseChatPie(methodName = "H3")
+ hookBaseChatPie(methodName = "P")
+ }
+ "8.9.8", "8.9.10" -> {
+ hookBaseChatPie(methodName = "H3")
+ hookBaseChatPie(methodName = "N")
+ }
+ else -> {
+ HookEntry.isHookClientSupport = false
+ loggerD(msg = "$appVersionName not supported!")
+ }
+ }
+ }
+
+ /**
+ * 拦截 [BaseChatPieClass] 的目标方法体封装
+ * @param methodName 方法名
+ */
+ private fun hookBaseChatPie(methodName: String) {
+ BaseChatPieClass.hook {
+ injectMember {
+ method {
+ name = methodName
+ emptyParam()
+ returnType = UnitType
+ }
+ intercept()
+ }
+ }
+ }
+
+ /** Hook CoreService QQ、TIM */
+ private fun hookCoreService() {
+ CoreServiceClass.hook {
+ if (isQQ) {
+ injectMember {
+ method { name = "startTempService" }
+ intercept()
+ }.ignoredNoSuchMemberFailure()
+ injectMember {
+ method {
+ name = "startCoreService"
+ param(BooleanType)
+ }
+ intercept()
+ }.ignoredNoSuchMemberFailure()
+ injectMember {
+ method {
+ name = "onStartCommand"
+ param(IntentClass, IntType, IntType)
+ }
+ replaceTo(any = 2)
+ }.ignoredNoSuchMemberFailure()
+ }
+ injectMember {
+ method { name = "onCreate" }
+ afterHook {
+ if (ConfigData.isEnableKillQQTimCoreService)
+ instance().apply {
+ ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
+ stopSelf()
+ loggerD(msg = "Shutdown CoreService OK!")
+ }
+ }
+ }
+ }
+ CoreService_KernelServiceClass.hook {
+ injectMember {
+ method { name = "onCreate" }
+ afterHook {
+ if (ConfigData.isEnableKillQQTimCoreServiceChild)
+ instance().apply {
+ ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
+ stopSelf()
+ loggerD(msg = "Shutdown CoreService\$KernelService OK!")
+ }
+ }
+ }
+ injectMember {
+ method {
+ name = "onStartCommand"
+ param(IntentClass, IntType, IntType)
+ }
+ replaceTo(any = 2)
+ }.ignoredNoSuchMemberFailure()
+ }
+ }
+
+ /** Hook QQ 不省电的功能 */
+ private fun hookQQDisgusting() {
+ if (isQQ.not()) return
+ /**
+ * 干掉消息收发功能的电源锁
+ * 每个版本的差异暂未做排查
+ * 旧版本理论上没有这个类
+ */
+ findClass(name = "${PackageName.QQ}.msf.service.y").hook {
+ injectMember {
+ method {
+ name = "a"
+ param(StringType, LongType)
+ returnType = UnitType
+ }
+ intercept()
+ }.onAllFailure { loggerE(msg = "Hook MsfService Failed $it") }
+ }.ignoredHookClassNotFoundFailure()
+ /**
+ * 干掉自动上传服务的电源锁
+ * 每个版本的差异暂未做排查
+ */
+ findClass(name = "com.tencent.upload.impl.UploadServiceImpl").hook {
+ injectMember {
+ method { name = "acquireWakeLockIfNot" }
+ intercept()
+ }.onAllFailure { loggerE(msg = "Hook UploadServiceImpl Failed $it") }
+ }.ignoredHookClassNotFoundFailure()
+ /**
+ * Hook 掉一个一像素保活 [Activity] 真的我怎么都想不到讯哥的程序员做出这种事情
+ * 这个东西经过测试会在锁屏的时候吊起来,解锁的时候自动 finish(),无限耍流氓耗电
+ * 2022/1/25 后期查证:锁屏界面消息快速回复窗口的解锁后拉起保活界面,也是毒瘤
+ */
+ findClass(name = "${PackageName.QQ}.activity.QQLSUnlockActivity").hook {
+ injectMember {
+ method {
+ name = "onCreate"
+ param(BundleClass)
+ }
+ var origDevice = ""
+ beforeHook {
+ /** 由于在 onCreate 里有一行判断只要型号是 xiaomi 的设备就开电源锁,所以说这里临时替换成菊花厂 */
+ origDevice = Build.MANUFACTURER
+ if (Build.MANUFACTURER.lowercase() == "xiaomi")
+ BuildClass.field { name = "MANUFACTURER" }.get().set("HUAWEI")
+ }
+ afterHook {
+ instance().finish()
+ /** 这里再把型号替换回去 - 不影响应用变量等 Xposed 模块修改的型号 */
+ BuildClass.field { name = "MANUFACTURER" }.get().set(origDevice)
+ }
+ }
+ }
+ /**
+ * 这个东西同上
+ * 反正也是一个一像素保活的 [Activity]
+ * 讯哥的程序员真的有你的
+ * 2022/1/25 后期查证:锁屏界面消息快速回复窗口
+ */
+ findClass("${PackageName.QQ}.activity.QQLSActivity\$14", "ktq").hook {
+ injectMember {
+ method { name = "run" }
+ intercept()
+ }.ignoredAllFailure()
+ }.ignoredHookClassNotFoundFailure()
+ /**
+ * 这个是毒瘤核心类
+ * WakeLockMonitor
+ * 这个名字真的起的特别诗情画意
+ * 带给用户的却是 shit 一样的体验
+ * 里面有各种使用 Handler 和 Timer 的各种耗时常驻后台耗电办法持续接收消息
+ * 直接循环全部方法全部干掉
+ * 👮🏻 经过排查 Play 版本没这个类...... Emmmm 不想说啥了
+ * ✅ 备注:8.9.x 版本已经基本移除了这个功能,没有再发现存在这个类
+ */
+ findClass(name = "com.tencent.qapmsdk.qqbattery.monitor.WakeLockMonitor").hook {
+ injectMember {
+ method {
+ name = "onHook"
+ param(StringType, AnyType, AnyArrayClass, AnyType)
+ }
+ intercept()
+ }
+ injectMember {
+ method {
+ name = "doReport"
+ param("com.tencent.qapmsdk.qqbattery.monitor.WakeLockMonitor\$WakeLockEntity", IntType)
+ }
+ intercept()
+ }
+ injectMember {
+ method {
+ name = "afterHookedMethod"
+ param("com.tencent.qapmsdk.qqbattery.monitor.MethodHookParam")
+ }
+ intercept()
+ }
+ injectMember {
+ method {
+ name = "beforeHookedMethod"
+ param("com.tencent.qapmsdk.qqbattery.monitor.MethodHookParam")
+ }
+ intercept()
+ }
+ injectMember {
+ method { name = "onAppBackground" }
+ intercept()
+ }
+ injectMember {
+ method {
+ name = "onOtherProcReport"
+ param(BundleClass)
+ }
+ intercept()
+ }
+ injectMember {
+ method { name = "onProcessRun30Min" }
+ intercept()
+ }
+ injectMember {
+ method { name = "onProcessBG5Min" }
+ intercept()
+ }
+ injectMember {
+ method {
+ name = "writeReport"
+ param(BooleanType)
+ }
+ intercept()
+ }
+ }.ignoredHookClassNotFoundFailure()
+ /**
+ * 这个是毒瘤核心操作类
+ * 功能同上、全部拦截
+ * 👮🏻 经过排查 Play 版本也没这个类...... Emmmm 不想说啥了
+ * ✅ 备注:8.9.x 版本已经基本移除了这个功能,没有再发现存在这个类
+ */
+ findClass(name = "com.tencent.qapmsdk.qqbattery.QQBatteryMonitor").hook {
+ injectMember {
+ method { name = "start" }
+ intercept()
+ }
+ injectMember {
+ method { name = "stop" }
+ intercept()
+ }
+ injectMember {
+ method {
+ name = "handleMessage"
+ param(MessageClass)
+ }
+ replaceToTrue()
+ }
+ injectMember {
+ method { name = "startMonitorInner" }
+ intercept()
+ }
+ injectMember {
+ method { name = "onAppBackground" }
+ intercept()
+ }
+ injectMember {
+ method { name = "onAppForeground" }
+ intercept()
+ }
+ injectMember {
+ method {
+ name = "setLogWhite"
+ paramCount = 2
+ }
+ intercept()
+ }
+ injectMember {
+ method {
+ name = "setCmdWhite"
+ paramCount = 2
+ }
+ intercept()
+ }
+ injectMember {
+ method {
+ name = "onWriteLog"
+ param(StringType, StringType)
+ }
+ intercept()
+ }
+ injectMember {
+ method {
+ name = "onCmdRequest"
+ param(StringType)
+ }
+ intercept()
+ }
+ injectMember {
+ method {
+ name = "addData"
+ paramCount = 4
+ }
+ intercept()
+ }
+ injectMember {
+ method {
+ name = "onGpsScan"
+ paramCount = 2
+ }
+ intercept()
+ }
+ }.ignoredHookClassNotFoundFailure()
+ }
+
+ override fun onHook() {
+ /** Hook 跳转事件 */
+ JumpActivityClass.hook {
+ injectMember {
+ method {
+ name = "doOnCreate"
+ param(BundleClass)
+ }
+ afterHook { instance().jumpToModuleSettings() }
+ }
+ }
+ /** 将条目注入设置界面 */
+ QQSettingSettingActivityClass.hook {
+ injectMember {
+ method {
+ name = "doOnCreate"
+ param(BundleClass)
+ }
+ afterHook {
+ /** 当前的顶级 Item 实例 */
+ val formItemRefRoot = field {
+ type(FormSimpleItemClass).index(num = 1)
+ }.ignored().get(instance).cast() ?: field {
+ type(FormCommonSingleLineItemClass).index(num = 1)
+ }.ignored().get(instance).cast()
+ /** 创建一个新的 Item */
+ FormSimpleItemClass.toClassOrNull()?.buildOf(instance) { param(ContextClass) }?.current {
+ method {
+ name = "setLeftText"
+ param(CharSequenceType)
+ }.call("TSBattery")
+ method {
+ name = "setRightText"
+ param(CharSequenceType)
+ }.call(BuildConfig.VERSION_NAME)
+ method {
+ name = "setBgType"
+ param(IntType)
+ }.call(if (isQQ) 0 else 2)
+ }?.apply { setOnClickListener { instance().startModuleSettings() } }?.also { item ->
+ var listGroup = formItemRefRoot?.parent as? ViewGroup?
+ val lparam = (if (listGroup?.childCount == 1) {
+ listGroup = listGroup.parent as? ViewGroup
+ (formItemRefRoot?.parent as? View?)?.layoutParams
+ } else formItemRefRoot?.layoutParams)
+ ?: ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+ /** 设置圆角和间距 */
+ if (isQQ) (lparam as? ViewGroup.MarginLayoutParams?)?.setMargins(0, 15.dp(item.context), 0, 0)
+ /** 将 Item 添加到设置界面 */
+ listGroup?.also { if (isQQ) it.addView(item, lparam) else it.addView(item, 0, lparam) }
+ }
+ }
+ }
+ }
+ if (ConfigData.isDisableAllHook) return
+ /** Hook 系统电源锁 */
+ hookSystemWakeLock()
+ /** Hook 聊天界面 */
+ hookQQBaseChatPie()
+ /** Hook CoreService */
+ hookCoreService()
+ /** Hook QQ 不省电的功能 */
+ hookQQDisgusting()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/fankes/tsbattery/hook/entity/WeChatHooker.kt b/app/src/main/java/com/fankes/tsbattery/hook/entity/WeChatHooker.kt
new file mode 100644
index 0000000..b3fbb77
--- /dev/null
+++ b/app/src/main/java/com/fankes/tsbattery/hook/entity/WeChatHooker.kt
@@ -0,0 +1,108 @@
+/*
+ * TSBattery - A new way to save your battery avoid cancer apps hacker it.
+ * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
+ * https://github.com/fankes/TSBattery
+ *
+ * This software is non-free but opensource software: you can redistribute it
+ * and/or modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * and eula along with this software. If not, see
+ *
+ *
+ * This file is Created by fankes on 2022/9/29.
+ */
+package com.fankes.tsbattery.hook.entity
+
+import android.app.Activity
+import android.os.Build
+import android.view.Gravity
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.LinearLayout
+import androidx.core.content.res.ResourcesCompat
+import com.fankes.tsbattery.R
+import com.fankes.tsbattery.const.PackageName
+import com.fankes.tsbattery.data.ConfigData
+import com.fankes.tsbattery.hook.factory.hookSystemWakeLock
+import com.fankes.tsbattery.hook.factory.jumpToModuleSettings
+import com.fankes.tsbattery.hook.factory.startModuleSettings
+import com.fankes.tsbattery.utils.factory.absoluteStatusBarHeight
+import com.fankes.tsbattery.utils.factory.dp
+import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
+import com.highcapable.yukihookapi.hook.factory.current
+import com.highcapable.yukihookapi.hook.factory.injectModuleAppResources
+import com.highcapable.yukihookapi.hook.log.loggerD
+import com.highcapable.yukihookapi.hook.type.android.BundleClass
+
+/**
+ * Hook 微信
+ *
+ * 具体功能还在画饼
+ */
+object WeChatHooker : YukiBaseHooker() {
+
+ /** 微信存在的类 - 未测试每个版本是否都存在 */
+ const val LauncherUIClass = "${PackageName.WECHAT}.ui.LauncherUI"
+
+ /** 微信存在的类 - 未测试每个版本是否都存在 */
+ private const val SettingsUIClass = "${PackageName.WECHAT}.plugin.setting.ui.setting.SettingsUI"
+
+ override fun onHook() {
+ /** Hook 跳转事件 */
+ LauncherUIClass.hook {
+ injectMember {
+ method {
+ name = "onResume"
+ emptyParam()
+ }
+ afterHook { instance().jumpToModuleSettings(isFinish = false) }
+ }
+ }
+ /** 向设置界面右上角添加按钮 */
+ SettingsUIClass.hook {
+ injectMember {
+ method {
+ name = "onCreate"
+ param(BundleClass)
+ }
+ afterHook {
+ method {
+ name = "get_fragment"
+ emptyParam()
+ superClass(isOnlySuperClass = true)
+ }.get(instance).call()?.current()
+ ?.field { name = "mController" }
+ ?.current()?.method { name = "getContentView" }
+ ?.invoke()?.addView(LinearLayout(instance()).apply {
+ context.injectModuleAppResources()
+ gravity = Gravity.END or Gravity.BOTTOM
+ layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+ addView(ImageView(context).apply {
+ layoutParams = ViewGroup.MarginLayoutParams(20.dp(context), 20.dp(context)).apply {
+ topMargin = context.absoluteStatusBarHeight + 15.dp(context)
+ rightMargin = 20.dp(context)
+ }
+ setColorFilter(ResourcesCompat.getColor(resources, R.color.colorTextGray, null))
+ setImageResource(R.drawable.ic_icon)
+ if (Build.VERSION.SDK_INT >= 26) tooltipText = "TSBattery 设置"
+ setOnClickListener { context.startModuleSettings() }
+ })
+ })
+ }
+ }
+ }
+ if (ConfigData.isDisableAllHook) return
+ /** Hook 系统电源锁 */
+ hookSystemWakeLock()
+ /** 日志省电大法 */
+ loggerD(msg = "ウイチャット:それが機能するかどうかはわかりませんでした")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/fankes/tsbattery/hook/factory/BasicHookFactory.kt b/app/src/main/java/com/fankes/tsbattery/hook/factory/BasicHookFactory.kt
new file mode 100644
index 0000000..d2ea3ab
--- /dev/null
+++ b/app/src/main/java/com/fankes/tsbattery/hook/factory/BasicHookFactory.kt
@@ -0,0 +1,63 @@
+/*
+ * TSBattery - A new way to save your battery avoid cancer apps hacker it.
+ * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
+ * https://github.com/fankes/TSBattery
+ *
+ * This software is non-free but opensource software: you can redistribute it
+ * and/or modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * and eula along with this software. If not, see
+ *
+ *
+ * This file is Created by fankes on 2022/9/29.
+ */
+package com.fankes.tsbattery.hook.factory
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import com.fankes.tsbattery.const.JumpEvent
+import com.fankes.tsbattery.ui.activity.parasitic.ConfigActivity
+import com.highcapable.yukihookapi.YukiHookAPI
+import com.highcapable.yukihookapi.hook.param.PackageParam
+import com.highcapable.yukihookapi.hook.type.android.PowerManager_WakeLockClass
+import kotlin.system.exitProcess
+
+/** 启动模块设置 [Activity] */
+fun Context.startModuleSettings() = startActivity(Intent(this, ConfigActivity::class.java))
+
+/**
+ * 跳转模块设置 [Activity]
+ * @param isFinish 执行完成是否自动关闭当前活动
+ */
+fun Activity.jumpToModuleSettings(isFinish: Boolean = true) {
+ if (intent.hasExtra(JumpEvent.OPEN_MODULE_SETTING)) {
+ /** 宿主版本不匹配的时候自动结束宿主进程 */
+ if (intent.getLongExtra(JumpEvent.OPEN_MODULE_SETTING, 0) != YukiHookAPI.Status.compiledTimestamp)
+ exitProcess(status = 0)
+ intent.removeExtra(JumpEvent.OPEN_MODULE_SETTING)
+ startModuleSettings()
+ if (isFinish) finish()
+ }
+}
+
+/** Hook 系统电源锁 */
+fun PackageParam.hookSystemWakeLock() {
+ PowerManager_WakeLockClass.hook {
+ injectMember {
+ method {
+ name = "acquireLocked"
+ emptyParam()
+ }
+ intercept()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/fankes/tsbattery/ui/activity/MainActivity.kt b/app/src/main/java/com/fankes/tsbattery/ui/activity/MainActivity.kt
index 08b9394..bc48b13 100644
--- a/app/src/main/java/com/fankes/tsbattery/ui/activity/MainActivity.kt
+++ b/app/src/main/java/com/fankes/tsbattery/ui/activity/MainActivity.kt
@@ -23,27 +23,27 @@
package com.fankes.tsbattery.ui.activity
+import android.content.ComponentName
+import android.content.Intent
import android.view.HapticFeedbackConstants
import androidx.core.view.isVisible
import com.fankes.tsbattery.BuildConfig
import com.fankes.tsbattery.R
-import com.fankes.tsbattery.data.DataConst
+import com.fankes.tsbattery.const.JumpEvent
+import com.fankes.tsbattery.const.PackageName
import com.fankes.tsbattery.databinding.ActivityMainBinding
-import com.fankes.tsbattery.hook.HookConst.QQ_PACKAGE_NAME
-import com.fankes.tsbattery.hook.HookConst.TIM_PACKAGE_NAME
-import com.fankes.tsbattery.hook.HookConst.WECHAT_PACKAGE_NAME
+import com.fankes.tsbattery.hook.entity.QQTIMHooker
+import com.fankes.tsbattery.hook.entity.WeChatHooker
import com.fankes.tsbattery.ui.activity.base.BaseActivity
import com.fankes.tsbattery.utils.factory.*
import com.fankes.tsbattery.utils.tool.GithubReleaseTool
import com.fankes.tsbattery.utils.tool.YukiPromoteTool
import com.highcapable.yukihookapi.YukiHookAPI
-import com.highcapable.yukihookapi.hook.factory.modulePrefs
class MainActivity : BaseActivity() {
companion object {
- private const val moduleVersion = BuildConfig.VERSION_NAME
private val qqSupportVersions = arrayOf(
"8.0.0", "8.0.5", "8.0.7", "8.1.0", "8.1.3", "8.1.5", "8.1.8",
"8.2.0", "8.2.6", "8.2.7", "8.2.8", "8.2.11", "8.3.0", "8.3.5",
@@ -71,7 +71,7 @@ class MainActivity : BaseActivity() {
override fun onCreate() {
/** 检查更新 */
- GithubReleaseTool.checkingForUpdate(context = this, moduleVersion) { version, function ->
+ GithubReleaseTool.checkingForUpdate(context = this, BuildConfig.VERSION_NAME) { version, function ->
binding.mainTextReleaseVersion.apply {
text = "点击更新 $version"
isVisible = true
@@ -90,38 +90,16 @@ class MainActivity : BaseActivity() {
} else
showDialog {
title = "模块没有激活"
- msg = "检测到模块没有激活,模块需要 Xposed 环境依赖," +
- "同时需要系统拥有 Root 权限(太极阴可以免 Root)," +
- "请自行查看本页面使用帮助与说明第三条。\n" +
- "太极和第三方 Xposed 激活后" +
- "可能不会提示激活,若想验证是否激活请打开“提示模块运行信息”自行检查," +
- "或观察 QQ、TIM 的常驻通知是否有“TSBattery 守护中”字样”。\n\n" +
- "如果生效就代表模块运行正常,若你在未 Root 情况下激活模块,这里的激活状态只是一个显示意义上的存在。\n" +
- "太极(无极)在 MIUI 设备上会提示打开授权,请进行允许,然后再次打开本模块查看激活状态。"
+ msg = "检测到模块没有激活,若你正在使用免 Root 框架例如 LSPatch、太极或无极,你可以忽略此提示。"
confirmButton(text = "我知道了")
noCancelable()
}
- /** 推荐使用 LSPosed */
- if (YukiHookAPI.Status.isTaiChiModuleActive)
- showDialog {
- title = "兼容性提示"
- msg = "若你的设备已 Root,推荐使用 LSPosed 激活模块,太极可能会出现模块设置无法保存的问题。"
- confirmButton(text = "我知道了")
- }
- /** 检测应用转生 - 如果模块已激活就不再检测 */
- if (("com.bug.xposed").isInstall && YukiHookAPI.Status.isModuleActive.not())
- showDialog {
- title = "环境异常"
- msg = "检测到“应用转生”已被安装,为了保证模块的安全和稳定,请卸载更换其他 Hook 框架后才能继续使用。"
- confirmButton(text = "退出") { finish() }
- noCancelable()
- }
/** 设置安装状态 */
- binding.mainTextQqVer.text = if (QQ_PACKAGE_NAME.isInstall) version(QQ_PACKAGE_NAME) else "未安装"
- binding.mainTextTimVer.text = if (TIM_PACKAGE_NAME.isInstall) version(TIM_PACKAGE_NAME) else "未安装"
- binding.mainTextWechatVer.text = if (WECHAT_PACKAGE_NAME.isInstall) version(WECHAT_PACKAGE_NAME) else "未安装"
+ binding.mainTextQqVer.text = if (PackageName.QQ.isInstall) version(PackageName.QQ) else "未安装"
+ binding.mainTextTimVer.text = if (PackageName.TIM.isInstall) version(PackageName.TIM) else "未安装"
+ binding.mainTextWechatVer.text = if (PackageName.WECHAT.isInstall) version(PackageName.WECHAT) else "未安装"
/** 设置文本 */
- binding.mainTextVersion.text = "模块版本:$moduleVersion $pendingFlag"
+ binding.mainTextVersion.text = "模块版本:${BuildConfig.VERSION_NAME} $pendingFlag"
binding.mainQqItem.setOnClickListener {
showDialog {
title = "兼容的 QQ 版本"
@@ -151,53 +129,16 @@ class MainActivity : BaseActivity() {
}
/** 获取 Sp 存储的信息 */
binding.hideIconInLauncherSwitch.isChecked = isLauncherIconShowing.not()
- binding.qqtimProtectModeSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_QQTIM_WHITE_MODE)
- binding.qqTimCoreServiceSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_QQTIM_CORESERVICE_BAN)
- binding.qqTimCoreServiceKnSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_QQTIM_CORESERVICE_CHILD_BAN)
- binding.wechatDisableHookSwitch.isChecked = modulePrefs.get(DataConst.DISABLE_WECHAT_HOOK)
- binding.notifyModuleInfoSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_RUN_INFO)
- binding.notifyNotifyTipSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_NOTIFY_TIP)
- binding.settingModuleTipSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_SETTING_TIP)
- binding.qqtimProtectModeSwitch.setOnCheckedChangeListener { btn, b ->
- if (btn.isPressed.not()) return@setOnCheckedChangeListener
- modulePrefs.put(DataConst.ENABLE_QQTIM_WHITE_MODE, b)
- snake(msg = "修改需要重启 QQ 以生效")
- }
- binding.qqTimCoreServiceSwitch.setOnCheckedChangeListener { btn, b ->
- if (btn.isPressed.not()) return@setOnCheckedChangeListener
- modulePrefs.put(DataConst.ENABLE_QQTIM_CORESERVICE_BAN, b)
- }
- binding.qqTimCoreServiceKnSwitch.setOnCheckedChangeListener { btn, b ->
- if (btn.isPressed.not()) return@setOnCheckedChangeListener
- modulePrefs.put(DataConst.ENABLE_QQTIM_CORESERVICE_CHILD_BAN, b)
- }
- binding.wechatDisableHookSwitch.setOnCheckedChangeListener { btn, b ->
- if (btn.isPressed.not()) return@setOnCheckedChangeListener
- modulePrefs.put(DataConst.DISABLE_WECHAT_HOOK, b)
- snake(msg = "修改需要重启微信以生效")
- }
binding.hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b ->
if (btn.isPressed.not()) return@setOnCheckedChangeListener
hideOrShowLauncherIcon(b)
}
- binding.notifyModuleInfoSwitch.setOnCheckedChangeListener { btn, b ->
- if (btn.isPressed.not()) return@setOnCheckedChangeListener
- modulePrefs.put(DataConst.ENABLE_RUN_INFO, b)
- }
- binding.notifyNotifyTipSwitch.setOnCheckedChangeListener { btn, b ->
- if (btn.isPressed.not()) return@setOnCheckedChangeListener
- modulePrefs.put(DataConst.ENABLE_NOTIFY_TIP, b)
- }
- binding.settingModuleTipSwitch.setOnCheckedChangeListener { btn, b ->
- if (btn.isPressed.not()) return@setOnCheckedChangeListener
- modulePrefs.put(DataConst.ENABLE_SETTING_TIP, b)
- }
/** 快捷操作 QQ */
- binding.quickQqButton.setOnClickListener { openSelfSetting(QQ_PACKAGE_NAME) }
+ binding.quickQqButton.setOnClickListener { startModuleSettings(PackageName.QQ) }
/** 快捷操作 TIM */
- binding.quickTimButton.setOnClickListener { openSelfSetting(TIM_PACKAGE_NAME) }
+ binding.quickTimButton.setOnClickListener { startModuleSettings(PackageName.TIM) }
/** 快捷操作微信 */
- binding.quickWechatButton.setOnClickListener { openSelfSetting(WECHAT_PACKAGE_NAME) }
+ binding.quickWechatButton.setOnClickListener { startModuleSettings(PackageName.WECHAT) }
/** 项目地址按钮点击事件 */
binding.titleGithubIcon.setOnClickListener { openBrowser(url = "https://github.com/fankes/TSBattery") }
/** 恰饭! */
@@ -206,6 +147,23 @@ class MainActivity : BaseActivity() {
}
}
+ /**
+ * 启动模块设置界面
+ * @param packageName 包名
+ */
+ private fun startModuleSettings(packageName: String) {
+ if (packageName.isInstall) runCatching {
+ startActivity(Intent().apply {
+ component = ComponentName(
+ packageName,
+ if (packageName != PackageName.WECHAT) QQTIMHooker.JumpActivityClass else WeChatHooker.LauncherUIClass
+ )
+ putExtra(JumpEvent.OPEN_MODULE_SETTING, YukiHookAPI.Status.compiledTimestamp)
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ })
+ }.onFailure { snake(msg = "启动模块设置失败\n$it") } else snake(msg = "你没有安装此应用")
+ }
+
/** 刷新模块激活使用的方式 */
private fun refreshActivateExecutor() {
when {
diff --git a/app/src/main/java/com/fankes/tsbattery/ui/activity/parasitic/ConfigActivity.kt b/app/src/main/java/com/fankes/tsbattery/ui/activity/parasitic/ConfigActivity.kt
new file mode 100644
index 0000000..32becf5
--- /dev/null
+++ b/app/src/main/java/com/fankes/tsbattery/ui/activity/parasitic/ConfigActivity.kt
@@ -0,0 +1,107 @@
+/*
+ * TSBattery - A new way to save your battery avoid cancer apps hacker it.
+ * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
+ * https://github.com/fankes/TSBattery
+ *
+ * This software is non-free but opensource software: you can redistribute it
+ * and/or modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * and eula along with this software. If not, see
+ *
+ *
+ * This file is Created by fankes on 2022/9/28.
+ */
+@file:Suppress("SetTextI18n")
+
+package com.fankes.tsbattery.ui.activity.parasitic
+
+import android.widget.TextView
+import androidx.core.view.isGone
+import androidx.core.view.isVisible
+import com.fankes.tsbattery.BuildConfig
+import com.fankes.tsbattery.const.PackageName
+import com.fankes.tsbattery.data.ConfigData
+import com.fankes.tsbattery.data.ConfigData.bind
+import com.fankes.tsbattery.databinding.ActivityConfigBinding
+import com.fankes.tsbattery.hook.HookEntry
+import com.fankes.tsbattery.ui.activity.base.BaseActivity
+import com.fankes.tsbattery.utils.factory.*
+import com.fankes.tsbattery.utils.tool.GithubReleaseTool
+import kotlin.system.exitProcess
+
+class ConfigActivity : BaseActivity() {
+
+ override fun onCreate() {
+ /** 检查更新 */
+ GithubReleaseTool.checkingForUpdate(context = this, BuildConfig.VERSION_NAME) { version, function ->
+ binding.updateVersionText.apply {
+ text = "点击更新 $version"
+ isVisible = true
+ setOnClickListener { function() }
+ }
+ }
+ binding.titleBackIcon.setOnClickListener { finish() }
+ binding.titleNameText.text = "TSBattery 设置 (${appName.trim()})"
+ binding.appIcon.setImageDrawable(findAppIcon())
+ binding.appName.text = appName.trim()
+ binding.appVersion.text = "${versionName}($versionCode)"
+ binding.moduleVersion.text = "${BuildConfig.VERSION_NAME}(${BuildConfig.VERSION_CODE})"
+ binding.activeModeIcon.isVisible = HookEntry.isHookClientSupport
+ binding.inactiveModeIcon.isGone = HookEntry.isHookClientSupport
+ binding.unsupportItem.isGone = HookEntry.isHookClientSupport
+ /** 刷新当前模式文本 */
+ fun refreshCurrentModeText() {
+ binding.currentModeText.text = when {
+ ConfigData.isDisableAllHook -> "模块已停用"
+ packageName == PackageName.WECHAT -> "仅限基础省电模式"
+ ConfigData.isEnableQQTimProtectMode -> "已启用保守模式"
+ else -> "已启用完全模式"
+ }
+ }
+ refreshCurrentModeText()
+ /** 刷新配置条目显示隐藏状态 */
+ fun refreshConfigItems() {
+ binding.itemQqTimConfig.isVisible = packageName != PackageName.WECHAT && ConfigData.isDisableAllHook.not()
+ }
+ refreshConfigItems()
+ binding.infoTipText.replaceToAppName()
+ binding.qqTimProtectTipText.replaceToAppName()
+ binding.disableAllHookSwitch.bind(ConfigData.DISABLE_ALL_HOOK) { refreshConfigItems(); refreshCurrentModeText(); showRestartDialog() }
+ binding.qqTimProtectModeSwitch.bind(ConfigData.ENABLE_QQ_TIM_PROTECT_MODE) { refreshCurrentModeText(); showRestartDialog() }
+ binding.qqTimCoreServiceSwitch.bind(ConfigData.ENABLE_KILL_QQ_TIM_CORESERVICE) { showRestartDialog() }
+ binding.qqTimCoreServiceChildSwitch.bind(ConfigData.ENABLE_KILLE_QQ_TIM_CORESERVICE_CHILD) { showRestartDialog() }
+ }
+
+ /** 显示重新启动对话框 */
+ private fun showRestartDialog() {
+ showDialog {
+ title = "需要重新启动"
+ msg = "你必须重新启动${appName}才能使当前更改生效,现在重新启动吗?"
+ confirmButton {
+ cancel()
+ finish()
+ exitProcess(status = 0)
+ }
+ cancelButton(text = "稍后再说")
+ }
+ }
+
+ /** 替换占位符到当前 APP 名称 */
+ private fun TextView.replaceToAppName() {
+ text = text.toString().replace(oldValue = "{APP_NAME}", appName)
+ }
+
+ /**
+ * 获取当前 APP 名称
+ * @return [String]
+ */
+ private val appName by lazy { findAppName().let { if (packageName == PackageName.WECHAT) it else " $it " } }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_config.xml b/app/src/main/res/layout/activity_config.xml
new file mode 100644
index 0000000..6b8f901
--- /dev/null
+++ b/app/src/main/res/layout/activity_config.xml
@@ -0,0 +1,372 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 2d8dc1d..f720032 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -7,7 +7,7 @@
android:background="@color/colorThemeBackground"
android:orientation="vertical"
tools:context=".ui.activity.MainActivity"
- tools:ignore="HardcodedText,UseCompoundDrawables,ContentDescription,TooManyViews">
+ tools:ignore="HardcodedText,UseCompoundDrawables,ContentDescription,UnusedAttribute">
+ android:tint="@color/colorTextGray"
+ android:tooltipText="项目地址" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -741,16 +504,6 @@
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
-
-