From 26023d53c62be03a63043a182ef0bc8c7116194d Mon Sep 17 00:00:00 2001 From: Fankesyooni Date: Tue, 15 Feb 2022 01:47:07 +0800 Subject: [PATCH] Refactor to YukiHookAPI https://github.com/fankes/YukiHookAPI --- .idea/misc.xml | 2 +- README.md | 1 + app/build.gradle | 21 +- app/src/main/AndroidManifest.xml | 13 +- app/src/main/assets/xposed_init | 2 +- app/src/main/assets/yukihookapi_init | 1 + .../XPrefUtils.kt => hook/HookConst.kt} | 28 +- .../com/fankes/tsbattery/hook/HookEntry.kt | 445 ++++++++++++++ .../com/fankes/tsbattery/hook/HookMain.kt | 562 ------------------ .../com/fankes/tsbattery/hook/HookMedium.kt | 182 ------ .../com/fankes/tsbattery/ui/MainActivity.kt | 106 +--- .../com/fankes/tsbattery/utils/FileUtils.java | 87 --- app/src/main/res/layout/activity_main.xml | 14 + build.gradle | 32 +- gradle/wrapper/gradle-wrapper.properties | 4 +- settings.gradle | 15 + 16 files changed, 548 insertions(+), 967 deletions(-) create mode 100644 app/src/main/assets/yukihookapi_init rename app/src/main/java/com/fankes/tsbattery/{utils/XPrefUtils.kt => hook/HookConst.kt} (57%) create mode 100644 app/src/main/java/com/fankes/tsbattery/hook/HookEntry.kt delete mode 100644 app/src/main/java/com/fankes/tsbattery/hook/HookMain.kt delete mode 100644 app/src/main/java/com/fankes/tsbattery/hook/HookMedium.kt delete mode 100755 app/src/main/java/com/fankes/tsbattery/utils/FileUtils.java diff --git a/.idea/misc.xml b/.idea/misc.xml index 3ea528d..bc08671 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -8,7 +8,7 @@ - + diff --git a/README.md b/README.md index 0c7564e..3c34e92 100644 --- a/README.md +++ b/README.md @@ -48,4 +48,5 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . ``` +Powered by [YukiHookAPI](https://github.com/fankes/YukiHookAPI)

版权所有 © 2019-2022 Fankes Studio(qzmmcn@163.com) diff --git a/app/build.gradle b/app/build.gradle index 6c766fb..bfef4f9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.application' id 'kotlin-android' + id 'com.google.devtools.ksp' version '1.6.10-1.0.2' } android { @@ -19,11 +20,10 @@ android { defaultConfig { applicationId "com.fankes.tsbattery" - minSdkVersion 22 - //noinspection ExpiredTargetSdkVersion - targetSdkVersion 26 - versionCode 10 - versionName "3.1" + minSdk 22 + targetSdk 31 + versionCode rootProject.ext.appVersionCode + versionName rootProject.ext.appVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -46,13 +46,10 @@ android { dependencies { compileOnly 'de.robv.android.xposed:api:82' - // 基础依赖包 - implementation 'com.gyf.immersionbar:immersionbar:3.0.0' - // Fragment 快速实现 - implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' - // Kotlin 扩展 - implementation 'com.gyf.immersionbar:immersionbar-ktx:3.0.0' - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'com.highcapable.yukihookapi:api:1.0' + ksp 'com.highcapable.yukihookapi:ksp-xposed:1.0' + implementation 'com.geyifeng.immersionbar:immersionbar:3.2.0' + implementation 'com.geyifeng.immersionbar:immersionbar-ktx:3.2.0' implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'com.google.android.material:material:1.5.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d41ed8a..92d4885 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,13 @@ xmlns:tools="http://schemas.android.com/tools" package="com.fankes.tsbattery"> + + + + + + + - - - + android:value="93" /> diff --git a/app/src/main/assets/xposed_init b/app/src/main/assets/xposed_init index d3e270c..962fff4 100644 --- a/app/src/main/assets/xposed_init +++ b/app/src/main/assets/xposed_init @@ -1 +1 @@ -com.fankes.tsbattery.hook.HookMain \ No newline at end of file +com.fankes.tsbattery.hook.HookEntry_YukiHookXposedInit \ No newline at end of file diff --git a/app/src/main/assets/yukihookapi_init b/app/src/main/assets/yukihookapi_init new file mode 100644 index 0000000..0db3259 --- /dev/null +++ b/app/src/main/assets/yukihookapi_init @@ -0,0 +1 @@ +com.fankes.tsbattery.hook.HookEntry \ No newline at end of file diff --git a/app/src/main/java/com/fankes/tsbattery/utils/XPrefUtils.kt b/app/src/main/java/com/fankes/tsbattery/hook/HookConst.kt similarity index 57% rename from app/src/main/java/com/fankes/tsbattery/utils/XPrefUtils.kt rename to app/src/main/java/com/fankes/tsbattery/hook/HookConst.kt index f09600d..4c54fe4 100644 --- a/app/src/main/java/com/fankes/tsbattery/utils/XPrefUtils.kt +++ b/app/src/main/java/com/fankes/tsbattery/hook/HookConst.kt @@ -18,23 +18,21 @@ * and eula along with this software. If not, see * * - * This file is Created by zpp0196 on 2018/4/11. + * This file is Created by fankes on 2021/11/9. */ -package com.fankes.tsbattery.utils +package com.fankes.tsbattery.hook -import de.robv.android.xposed.XSharedPreferences +object HookConst { -object XPrefUtils { + const val ENABLE_HIDE_ICON = "_hide_icon" + const val ENABLE_RUN_INFO = "_tip_run_info" + const val ENABLE_QQTIM_WHITE_MODE = "_qqtim_white_mode" + const val ENABLE_QQTIM_CORESERVICE_BAN = "_qqtim_core_service_ban" + const val ENABLE_QQTIM_CORESERVICE_CHILD_BAN = "_qqtim_core_service_child_ban" + const val DISABLE_WECHAT_HOOK = "_disable_wechat_hook" + const val ENABLE_MODULE_VERSION = "_module_version" - fun getBoolean(key: String, default: Boolean = false) = pref.getBoolean(key, default) - - fun getString(key: String, default: String = "unknown") = pref.getString(key, default) - - private val pref: XSharedPreferences - get() { - val preferences = XSharedPreferences("com.fankes.tsbattery") - preferences.makeWorldReadable() - preferences.reload() - return preferences - } + const val QQ_PACKAGE_NAME = "com.tencent.mobileqq" + const val TIM_PACKAGE_NAME = "com.tencent.tim" + const val WECHAT_PACKAGE_NAME = "com.tencent.mm" } \ 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 new file mode 100644 index 0000000..219627d --- /dev/null +++ b/app/src/main/java/com/fankes/tsbattery/hook/HookEntry.kt @@ -0,0 +1,445 @@ +/* + * 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 and our eula as published + * by ferredoxin. + * + * 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/2/15. + */ +package com.fankes.tsbattery.hook + +import android.app.Activity +import android.app.Service +import android.content.Intent +import android.os.Build +import com.fankes.tsbattery.hook.HookConst.DISABLE_WECHAT_HOOK +import com.fankes.tsbattery.hook.HookConst.ENABLE_MODULE_VERSION +import com.fankes.tsbattery.hook.HookConst.ENABLE_QQTIM_CORESERVICE_BAN +import com.fankes.tsbattery.hook.HookConst.ENABLE_QQTIM_CORESERVICE_CHILD_BAN +import com.fankes.tsbattery.hook.HookConst.ENABLE_QQTIM_WHITE_MODE +import com.fankes.tsbattery.hook.HookConst.ENABLE_RUN_INFO +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.utils.showDialog +import com.fankes.tsbattery.utils.versionCode +import com.fankes.tsbattery.utils.versionName +import com.highcapable.yukihookapi.YukiHookAPI.configs +import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed +import com.highcapable.yukihookapi.hook.bean.VariousClass +import com.highcapable.yukihookapi.hook.factory.encase +import com.highcapable.yukihookapi.hook.factory.modifyStaticField +import com.highcapable.yukihookapi.hook.log.loggerD +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.YukiHookXposedInitProxy + +@InjectYukiHookWithXposed +class HookEntry : YukiHookXposedInitProxy { + + companion object { + + /** BaseChatPie 类名 */ + private val QQ_BASE_CHAT_PIE = + VariousClass("$QQ_PACKAGE_NAME.activity.aio.core.BaseChatPie", "$QQ_PACKAGE_NAME.activity.BaseChatPie") + } + + /** + * 这个类 QQ 的 BaseChatPie 是控制聊天界面的 + * + * 里面有两个随机混淆的方法 ⬇ + * + * remainScreenOn、cancelRemainScreenOn + * + * 这两个方法一个是挂起电源锁常驻亮屏 + * + * 一个是停止常驻亮屏 + * + * 不由分说每个版本混淆的方法名都会变 + * + * 所以说每个版本重新适配 - 也可以提交分支帮我适配 + * + * - ❗Hook 错了方法会造成闪退! + * @param version QQ 版本 + */ + private fun PackageParam.hookQQBaseChatPie(version: String) { + when (version) { + "8.2.11" -> { + interceptBaseChatPie(methodName = "bE") + interceptBaseChatPie(methodName = "aV") + } + "8.8.17" -> { + interceptBaseChatPie(methodName = "bd") + interceptBaseChatPie(methodName = "be") + } + "8.8.23" -> { + interceptBaseChatPie(methodName = "bf") + interceptBaseChatPie(methodName = "bg") + } + /** 8.8.35 贡献者:StarWishsama */ + "8.8.35", "8.8.38" -> { + interceptBaseChatPie(methodName = "bi") + interceptBaseChatPie(methodName = "bj") + } + /** 贡献者:JiZhi-Error */ + "8.8.50" -> { + interceptBaseChatPie(methodName = "bj") + interceptBaseChatPie(methodName = "bk") + } + "8.8.55", "8.8.68" -> { + interceptBaseChatPie(methodName = "bk") + interceptBaseChatPie(methodName = "bl") + } + else -> loggerD(msg = "$version not supported!") + } + } + + /** + * 拦截 [QQ_BASE_CHAT_PIE] 的目标方法体封装 + * @param methodName 方法名 + */ + private fun PackageParam.interceptBaseChatPie(methodName: String) = + findClass(QQ_BASE_CHAT_PIE).hook { + injectMember(tag = "BaseChatPie") { + method { + name = methodName + returnType = UnitType + } + intercept() + } + } + + /** Hook 系统电源锁 */ + private fun PackageParam.hookSystemWakeLock() = + PowerManager_WakeLockClass.hook { + injectMember(tag = "WakeLock acquire") { + method { + name = "acquireLocked" + returnType = UnitType + } + intercept() + } + } + + /** 增加通知栏文本显示守护状态 */ + private fun PackageParam.hookNotification() = + Notification_BuilderClass.hook { + injectMember(tag = "Notification") { + method { + name = "setContentText" + param(CharSequenceType) + } + beforeHook { + when (args[0] as CharSequence) { + "QQ正在后台运行" -> + args().set("QQ正在后台运行 - TSBattery 守护中") + "TIM正在后台运行" -> + args().set("TIM正在后台运行 - TSBattery 守护中") + } + } + } + } + + /** + * 提示模块运行信息 QQ、TIM、微信 + * @param isQQTIM 是否为 QQ、TIM + */ + private fun PackageParam.hookModuleRunningInfo(isQQTIM: Boolean) = + when { + !prefs.getBoolean(ENABLE_RUN_INFO) -> {} + isQQTIM -> + findClass(name = "$QQ_PACKAGE_NAME.activity.SplashActivity").hook { + /** + * Hook 启动界面的第一个 [Activity] + * QQ 和 TIM 都是一样的类 + * 在里面加入提示运行信息的对话框测试模块是否激活 + */ + injectMember(tag = "SplashActivity") { + method { + name = "doOnCreate" + param(BundleClass) + } + afterHook { + instance().apply { + showDialog { + title = "TSBattery 已激活" + msg = "[提示模块运行信息功能已打开]\n\n" + + "模块工作看起来一切正常,请自行测试是否能达到省电效果。\n\n" + + "已生效模块版本:${prefs.getString(ENABLE_MODULE_VERSION)}\n" + + "当前模式:${if (prefs.getBoolean(ENABLE_QQTIM_WHITE_MODE)) "保守模式" else "完全模式"}" + + "\n\n包名:${packageName}\n版本:$versionName($versionCode)" + + "\n\n模块只对挂后台锁屏情况下有省电效果," + + "请不要将过多的群提醒,消息通知打开,这样子在使用过程时照样会极其耗电。\n\n" + + "如果你不想看到此提示。请在模块设置中关闭“提示模块运行信息”,此设置默认关闭。\n\n" + + "持续常驻使用 QQ 依然会耗电,任何软件都是如此," + + "模块无法帮你做到前台不耗电,永远记住这一点。\n\n" + + "开发者 酷安 @星夜不荟\n未经允许禁止转载、修改或复制我的劳动成果。" + confirmButton(text = "我知道了") + noCancelable() + } + } + } + } + } + else -> + findClass(name = "$WECHAT_PACKAGE_NAME.ui.LauncherUI").hook { + /** + * Hook 启动界面的第一个 [Activity] + * 在里面加入提示运行信息的对话框测试模块是否激活 + */ + injectMember(tag = "LauncherUI") { + method { + name = "onCreate" + param(BundleClass) + } + afterHook { + instance().apply { + showDialog(isUseBlackTheme = true) { + title = "TSBattery 已激活" + msg = "[提示模块运行信息功能已打开]\n\n" + + "模块工作看起来一切正常,请自行测试是否能达到省电效果。\n\n" + + "已生效模块版本:${prefs.getString(ENABLE_MODULE_VERSION)}\n" + + "当前模式:基础省电" + + "\n\n包名:${packageName}\n版本:$versionName($versionCode)" + + "\n\n当前只支持微信的基础省电,即系统电源锁,后续会继续适配微信相关的省电功能(在新建文件夹了)。\n\n" + + "如果你不想看到此提示。请在模块设置中关闭“提示模块运行信息”,此设置默认关闭。\n\n" + + "持续常驻使用微信依然会耗电,任何软件都是如此," + + "模块无法帮你做到前台不耗电,永远记住这一点。\n\n" + + "开发者 酷安 @星夜不荟\n未经允许禁止转载、修改或复制我的劳动成果。" + confirmButton(text = "我知道了") + noCancelable() + } + } + } + } + } + } + + /** + * Hook CoreService QQ、TIM + * @param isQQ 是否为 QQ - 单独处理 + */ + private fun PackageParam.hookCoreService(isQQ: Boolean) { + if (prefs.getBoolean(ENABLE_QQTIM_CORESERVICE_BAN)) + findClass(name = "$QQ_PACKAGE_NAME.app.CoreService").hook { + if (isQQ) { + injectMember(tag = "CoreService startTemp") { + method { name = "startTempService" } + intercept() + } + injectMember(tag = "CoreService startCore") { + method { + name = "startCoreService" + param(BooleanType) + } + intercept() + } + injectMember(tag = "CoreService startCommand") { + method { + name = "onStartCommand" + param(IntentClass, IntType, IntType) + } + replaceTo(any = 2) + } + } + injectMember(tag = "CoreService onCreate") { + method { name = "onCreate" } + afterHook { + instance().apply { + stopForeground(true) + stopService(Intent(applicationContext, javaClass)) + loggerD(msg = "Shutdown CoreService OK!") + } + } + } + } + if (prefs.getBoolean(ENABLE_QQTIM_CORESERVICE_CHILD_BAN)) + findClass(name = "$QQ_PACKAGE_NAME.app.CoreService\$KernelService").hook { + injectMember(tag = "CoreService\$KernelService onCreate") { + method { name = "onCreate" } + afterHook { + instance().apply { + stopForeground(true) + stopService(Intent(applicationContext, javaClass)) + loggerD(msg = "Shutdown CoreService\$KernelService OK!") + } + } + } + injectMember(tag = "CoreService\$KernelService startCommand") { + method { + name = "onStartCommand" + param(IntentClass, IntType, IntType) + } + replaceTo(any = 2) + } + } + } + + override fun onHook() = encase { + configs { + debugTag = "TSBattery" + isDebug = false + } + loadApp(QQ_PACKAGE_NAME) { + hookSystemWakeLock() + hookNotification() + hookCoreService(isQQ = true) + hookModuleRunningInfo(isQQTIM = true) + if (prefs.getBoolean(ENABLE_QQTIM_WHITE_MODE)) return@loadApp + /** 通过在 SplashActivity 里取到应用的版本号 */ + findClass(name = "$QQ_PACKAGE_NAME.activity.SplashActivity").hook { + injectMember(tag = "BaseChatPie(first time)") { + method { + name = "doOnCreate" + param(BundleClass) + } + afterHook { hookQQBaseChatPie(instance().versionName) } + } + } + /** + * 一个不知道是什么作用的电源锁 + * 同样直接干掉 + */ + findClass(name = "com.tencent.mars.ilink.comm.WakerLock").hook { + injectMember(tag = "WakerLock") { + method { + name = "lock" + param(LongType) + } + intercept() + }.ignoredAllFailure() + } + /** + * Hook 掉一个一像素保活 [Activity] 真的我怎么都想不到讯哥的程序员做出这种事情 + * 这个东西经过测试会在锁屏的时候吊起来,解锁的时候自动 finish(),无限耍流氓耗电 + * 2022/1/25 后期查证:锁屏界面消息快速回复窗口的解锁后拉起保活界面,也是毒瘤 + */ + findClass(name = "$QQ_PACKAGE_NAME.activity.QQLSUnlockActivity").hook { + injectMember(tag = "QQLSActivity") { + method { + name = "onCreate" + param(BundleClass) + } + var origDevice = "" + beforeHook { + /** 由于在 onCreate 里有一行判断只要型号是 xiaomi 的设备就开电源锁,所以说这里临时替换成菊花厂 */ + origDevice = Build.MANUFACTURER + if (Build.MANUFACTURER.lowercase() == "xiaomi") + BuildClass.modifyStaticField(name = "MANUFACTURER", value = "HUAWEI") + } + afterHook { + instance().finish() + /** 这里再把型号替换回去 - 不影响应用变量等 Xposed 模块修改的型号 */ + BuildClass.modifyStaticField(name = "MANUFACTURER", origDevice) + } + } + } + /** + * 这个东西同上 + * 反正也是一个一像素保活的 [Activity] + * 讯哥的程序员真的有你的 + * 2022/1/25 后期查证:锁屏界面消息快速回复窗口 + */ + findClass(name = "$QQ_PACKAGE_NAME.activity.QQLSActivity\$14").hook { + injectMember(tag = "QQLSActivity\$14") { + method { name = "run" } + intercept() + }.ignoredAllFailure() + } + /** + * 这个是毒瘤核心类 + * WakeLockMonitor + * 这个名字真的起的特别诗情画意 + * 带给用户的却是 shit 一样的体验 + * 里面有各种使用 Handler 和 Timer 的各种耗时常驻后台耗电办法持续接收消息 + * 直接循环全部方法全部干掉 + * 👮🏻 经过排查 Play 版本没这个类...... Emmmm 不想说啥了 + */ + findClass(name = "com.tencent.qapmsdk.qqbattery.monitor.WakeLockMonitor").hook { + ("WakeLockMonitor").also { tag -> + injectMember(tag) { + method { + name = "onHook" + param(StringType, AnyType, AnyArrayClass(AnyType), AnyType) + } + intercept() + } + injectMember(tag) { + method { + name = "doReport" + param(("com.tencent.qapmsdk.qqbattery.monitor.WakeLockMonitor\$WakeLockEntity").clazz, IntType) + } + intercept() + } + injectMember(tag) { + method { + name = "afterHookedMethod" + param(("com.tencent.qapmsdk.qqbattery.monitor.MethodHookParam").clazz) + } + intercept() + } + injectMember(tag) { + method { + name = "beforeHookedMethod" + param(("com.tencent.qapmsdk.qqbattery.monitor.MethodHookParam").clazz) + } + intercept() + } + injectMember(tag) { + method { name = "onAppBackground" } + intercept() + } + injectMember(tag) { + method { + name = "onOtherProcReport" + param(BundleClass) + } + intercept() + } + injectMember(tag) { + method { name = "onProcessRun30Min" } + intercept() + } + injectMember(tag) { + method { name = "onProcessBG5Min" } + intercept() + } + injectMember(tag) { + method { + name = "writeReport" + param(BooleanType) + } + intercept() + } + } + } + } + loadApp(TIM_PACKAGE_NAME) { + hookSystemWakeLock() + hookNotification() + hookCoreService(isQQ = false) + hookModuleRunningInfo(isQQTIM = true) + } + loadApp(WECHAT_PACKAGE_NAME) { + if (prefs.getBoolean(DISABLE_WECHAT_HOOK)) return@loadApp + hookSystemWakeLock() + hookModuleRunningInfo(isQQTIM = false) + loggerD(msg = "ウイチャット:それが機能するかどうかはわかりませんでした") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/tsbattery/hook/HookMain.kt b/app/src/main/java/com/fankes/tsbattery/hook/HookMain.kt deleted file mode 100644 index 88cc5ea..0000000 --- a/app/src/main/java/com/fankes/tsbattery/hook/HookMain.kt +++ /dev/null @@ -1,562 +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 and our eula as published - * by ferredoxin. - * - * 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 2021/9/4. - */ -@file:Suppress("DEPRECATION", "SameParameterValue") - -package com.fankes.tsbattery.hook - -import android.app.Activity -import android.app.Service -import android.content.Intent -import android.os.Build -import android.os.Bundle -import android.util.Log -import androidx.annotation.Keep -import com.fankes.tsbattery.hook.HookMedium.QQ_PACKAGE_NAME -import com.fankes.tsbattery.hook.HookMedium.SELF_PACKAGE_NAME -import com.fankes.tsbattery.hook.HookMedium.TIM_PACKAGE_NAME -import com.fankes.tsbattery.hook.HookMedium.WECHAT_PACKAGE_NAME -import com.fankes.tsbattery.utils.showDialog -import com.fankes.tsbattery.utils.versionCode -import com.fankes.tsbattery.utils.versionName -import de.robv.android.xposed.* -import de.robv.android.xposed.callbacks.XC_LoadPackage -import java.util.* - -@Keep -class HookMain : IXposedHookLoadPackage { - - companion object { - - /** 旧版类名 */ - private const val BASE_CHAT_PIE_LEGACY = "activity.BaseChatPie" - - /** 新版类名 */ - private const val BASE_CHAT_PIE = "activity.aio.core.BaseChatPie" - } - - /** 仅作用于替换的 Hook 方法体 */ - private val replaceToNull = object : XC_MethodReplacement() { - override fun replaceHookedMethod(param: MethodHookParam?): Any? { - return null - } - } - - /** 仅作用于替换的 Hook 方法体 */ - private val replaceToTrue = object : XC_MethodReplacement() { - override fun replaceHookedMethod(param: MethodHookParam?): Any { - return true - } - } - - /** - * 干掉目标方法体封装 - * @param clazz 类名缩写 - * @param name 方法名 - */ - private fun XC_LoadPackage.LoadPackageParam.replaceToNull(clazz: String, name: String) { - XposedHelpers.findAndHookMethod( - "$QQ_PACKAGE_NAME.$clazz", - classLoader, - name, - replaceToNull - ) - } - - /** - * 忽略异常运行 - * @param it 正常回调 - */ - private fun runWithoutError(error: String, it: () -> Unit) { - try { - it() - } catch (e: Error) { - logE("hookFailed: $error", e) - } catch (e: Exception) { - logE("hookFailed: $error", e) - } catch (e: Throwable) { - logE("hookFailed: $error", e) - } - } - - /** - * 这个类 QQ 的 BaseChatPie 是控制聊天界面的 - * - * 里面有两个随机混淆的方法 ⬇️ - * - * remainScreenOn、cancelRemainScreenOn - * - * 这两个方法一个是挂起电源锁常驻亮屏 - * - * 一个是停止常驻亮屏 - * - * 不由分说每个版本混淆的方法名都会变 - * - * 所以说每个版本重新适配 - 也可以提交分支帮我适配 - * - * - Hook 错了方法会造成闪退! - * @param version QQ 版本 - */ - private fun XC_LoadPackage.LoadPackageParam.hookQQBaseChatPie(version: String) { - when (version) { - "8.2.11" -> { - replaceToNull(BASE_CHAT_PIE_LEGACY, "bE") - replaceToNull(BASE_CHAT_PIE_LEGACY, "aV") - } - "8.8.17" -> { - replaceToNull(BASE_CHAT_PIE, "bd") - replaceToNull(BASE_CHAT_PIE, "be") - } - "8.8.23" -> { - replaceToNull(BASE_CHAT_PIE, "bf") - replaceToNull(BASE_CHAT_PIE, "bg") - } - /** 8.8.35 贡献者:StarWishsama */ - "8.8.35", "8.8.38" -> { - replaceToNull(BASE_CHAT_PIE, "bi") - replaceToNull(BASE_CHAT_PIE, "bj") - } - /** 贡献者:JiZhi-Error */ - "8.8.50" -> { - replaceToNull(BASE_CHAT_PIE, "bj") - replaceToNull(BASE_CHAT_PIE, "bk") - } - "8.8.55", "8.8.68" -> { - replaceToNull(BASE_CHAT_PIE, "bk") - replaceToNull(BASE_CHAT_PIE, "bl") - } - else -> logD("$version not supported!") - } - } - - /** - * Print the log - * @param content - */ - private fun logD(content: String) { - XposedBridge.log("[TSBattery][D]>$content") - Log.d("TSBattery", content) - } - - /** - * Print the log - * @param content - */ - private fun logE(content: String, e: Throwable? = null) { - XposedBridge.log("[TSBattery][E]>$content") - XposedBridge.log(e) - Log.e("TSBattery", content, e) - } - - /** Hook 系统电源锁 */ - private fun XC_LoadPackage.LoadPackageParam.hookSystemWakeLock() { - runWithoutError("wakeLock acquire()") { - XposedHelpers.findAndHookMethod( - "android.os.PowerManager\$WakeLock", - classLoader, - "acquire", - replaceToNull - ) - } - runWithoutError("hook wakeLock acquire(time)") { - XposedHelpers.findAndHookMethod( - "android.os.PowerManager\$WakeLock", - classLoader, - "acquire", - Long::class.java, - replaceToNull - ) - } - } - - /** 增加通知栏文本显示守护状态 */ - private fun XC_LoadPackage.LoadPackageParam.hookNotification() = - runWithoutError("Notification") { - XposedHelpers.findAndHookMethod( - "android.app.Notification\$Builder", - classLoader, - "setContentText", - CharSequence::class.java, - object : XC_MethodHook() { - override fun beforeHookedMethod(param: MethodHookParam?) { - when (param?.args?.get(0) as? CharSequence?) { - "QQ正在后台运行" -> - param.args?.set(0, "QQ正在后台运行 - TSBattery 守护中") - "TIM正在后台运行" -> - param.args?.set(0, "TIM正在后台运行 - TSBattery 守护中") - } - } - }) - } - - /** 提示模块运行信息 QQ、TIM、微信 */ - private fun XC_LoadPackage.LoadPackageParam.hookModuleRunningInfo() = - if (packageName != WECHAT_PACKAGE_NAME) - runWithoutError("SplashActivity") { - /** 判断是否开启提示模块运行信息 */ - if (HookMedium.getBoolean(HookMedium.ENABLE_RUN_INFO)) - XposedHelpers.findAndHookMethod( - "$QQ_PACKAGE_NAME.activity.SplashActivity", - classLoader, - "doOnCreate", - Bundle::class.java, - object : XC_MethodHook() { - - override fun afterHookedMethod(param: MethodHookParam?) { - /** - * Hook 启动界面的第一个 [Activity] - * QQ 和 TIM 都是一样的类 - * 在里面加入提示运行信息的对话框测试模块是否激活 - */ - (param?.thisObject as? Activity?)?.apply { - showDialog { - title = "TSBattery 已激活" - msg = "[提示模块运行信息功能已打开]\n\n" + - "模块工作看起来一切正常,请自行测试是否能达到省电效果。\n\n" + - "已生效模块版本:${HookMedium.getString(HookMedium.ENABLE_MODULE_VERSION)}\n" + - "当前模式:${if (HookMedium.getBoolean(HookMedium.ENABLE_QQTIM_WHITE_MODE)) "保守模式" else "完全模式"}" + - "\n\n包名:${packageName}\n版本:$versionName($versionCode)" + - "\n\n模块只对挂后台锁屏情况下有省电效果,请不要将过多的群提醒,消息通知打开,这样子在使用过程时照样会极其耗电。\n\n" + - "如果你不想看到此提示。请在模块设置中关闭“提示模块运行信息”,此设置默认关闭。\n\n" + - "持续常驻使用 QQ 依然会耗电,任何软件都是如此,模块无法帮你做到前台不耗电,永远记住这一点。\n\n" + - "开发者 酷安 @星夜不荟\n未经允许禁止转载、修改或复制我的劳动成果。" - confirmButton(text = "我知道了") - noCancelable() - } - } - } - }) - } - else runWithoutError("LauncherUI") { - /** 判断是否开启提示模块运行信息 */ - if (HookMedium.getBoolean(HookMedium.ENABLE_RUN_INFO)) - XposedHelpers.findAndHookMethod( - "$WECHAT_PACKAGE_NAME.ui.LauncherUI", - classLoader, - "onCreate", - Bundle::class.java, - object : XC_MethodHook() { - - override fun afterHookedMethod(param: MethodHookParam?) { - /** - * Hook 启动界面的第一个 [Activity] - * 在里面加入提示运行信息的对话框测试模块是否激活 - */ - (param?.thisObject as? Activity?)?.apply { - showDialog(isUseBlackTheme = true) { - title = "TSBattery 已激活" - msg = "[提示模块运行信息功能已打开]\n\n" + - "模块工作看起来一切正常,请自行测试是否能达到省电效果。\n\n" + - "已生效模块版本:${HookMedium.getString(HookMedium.ENABLE_MODULE_VERSION)}\n" + - "当前模式:基础省电" + - "\n\n包名:${packageName}\n版本:$versionName($versionCode)" + - "\n\n当前只支持微信的基础省电,即系统电源锁,后续会继续适配微信相关的省电功能(在新建文件夹了)。\n\n" + - "如果你不想看到此提示。请在模块设置中关闭“提示模块运行信息”,此设置默认关闭。\n\n" + - "持续常驻使用微信依然会耗电,任何软件都是如此,模块无法帮你做到前台不耗电,永远记住这一点。\n\n" + - "开发者 酷安 @星夜不荟\n未经允许禁止转载、修改或复制我的劳动成果。" - confirmButton(text = "我知道了") - noCancelable() - } - } - } - }) - } - - /** Hook CoreService QQ、TIM */ - private fun XC_LoadPackage.LoadPackageParam.hookCoreService() { - /** Hook CoreService 指定方法 */ - if (packageName == QQ_PACKAGE_NAME) - runWithoutError("CoreServiceKnownMethods") { - if (HookMedium.getBoolean(HookMedium.ENABLE_QQTIM_CORESERVICE_BAN)) { - XposedHelpers.findAndHookMethod( - "$QQ_PACKAGE_NAME.app.CoreService", - classLoader, "startTempService", replaceToNull - ) - XposedHelpers.findAndHookMethod( - "$QQ_PACKAGE_NAME.app.CoreService", - classLoader, "startCoreService", Boolean::class.java, replaceToNull - ) - XposedHelpers.findAndHookMethod( - "$QQ_PACKAGE_NAME.app.CoreService", - classLoader, - "onStartCommand", - Intent::class.java, Int::class.java, Int::class.java, - object : XC_MethodReplacement() { - - override fun replaceHookedMethod(param: MethodHookParam?) = 2 - }) - logD("hook CoreService OK!") - } - } - /** Hook CoreService 启动方法 */ - runWithoutError("CoreService") { - if (HookMedium.getBoolean(HookMedium.ENABLE_QQTIM_CORESERVICE_BAN)) { - XposedHelpers.findAndHookMethod( - "$QQ_PACKAGE_NAME.app.CoreService", - classLoader, "onCreate", - object : XC_MethodHook() { - - override fun afterHookedMethod(param: MethodHookParam?) { - (param?.thisObject as? Service)?.apply { - runWithoutError("StopCoreService") { - stopForeground(true) - stopService(Intent(applicationContext, javaClass)) - logD("Shutdown CoreService OK!") - } - } - } - }) - logD("hook CoreService [onCreate] OK!") - } - } - /** Hook CoreService$KernelService 启动方法 */ - runWithoutError("CoreService\$KernelService") { - if (HookMedium.getBoolean(HookMedium.ENABLE_QQTIM_CORESERVICE_CHILD_BAN)) { - XposedHelpers.findAndHookMethod( - "$QQ_PACKAGE_NAME.app.CoreService\$KernelService", - classLoader, "onCreate", - object : XC_MethodHook() { - - override fun afterHookedMethod(param: MethodHookParam?) { - (param?.thisObject as? Service)?.apply { - runWithoutError("StopKernelService") { - stopForeground(true) - stopService(Intent(applicationContext, javaClass)) - logD("Shutdown CoreService\$KernelService OK!") - } - } - } - }) - XposedHelpers.findAndHookMethod( - "$QQ_PACKAGE_NAME.app.CoreService\$KernelService", - classLoader, - "onStartCommand", - Intent::class.java, Int::class.java, Int::class.java, - object : XC_MethodReplacement() { - - override fun replaceHookedMethod(param: MethodHookParam?) = 2 - }) - logD("hook CoreService\$KernelService [onCreate] OK!") - } - } - } - - override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam?) { - if (lpparam == null) return - when (lpparam.packageName) { - /** Hook 自身 */ - SELF_PACKAGE_NAME -> - XposedHelpers.findAndHookMethod( - "$SELF_PACKAGE_NAME.hook.HookMedium", - lpparam.classLoader, - "isHooked", - replaceToTrue - ) - /** Hook TIM */ - TIM_PACKAGE_NAME -> - lpparam.apply { - hookSystemWakeLock() - hookNotification() - hookModuleRunningInfo() - hookCoreService() - logD("hook Completed!") - } - /** Hook QQ */ - QQ_PACKAGE_NAME -> { - lpparam.apply { - hookSystemWakeLock() - hookNotification() - hookModuleRunningInfo() - hookCoreService() - } - /** 关闭保守模式后不再仅仅作用于系统电源锁 */ - if (!HookMedium.getBoolean(HookMedium.ENABLE_QQTIM_WHITE_MODE)) { - runWithoutError("BaseChatPie(first time)") { - /** 通过在 SplashActivity 里取到应用的版本号 */ - XposedHelpers.findAndHookMethod( - "$QQ_PACKAGE_NAME.activity.SplashActivity", - lpparam.classLoader, - "doOnCreate", - Bundle::class.java, - object : XC_MethodHook() { - - override fun beforeHookedMethod(param: MethodHookParam?) { - val self = param?.thisObject as? Activity ?: return - val version = self.versionName - runWithoutError("BaseChatPie") { lpparam.hookQQBaseChatPie(version) } - } - }) - } - runWithoutError("WakerLock") { - /** - * 一个不知道是什么作用的电源锁 - * 同样直接干掉 - */ - XposedHelpers.findAndHookMethod( - "com.tencent.mars.ilink.comm.WakerLock", - lpparam.classLoader, - "lock", Long::class.java, - replaceToNull - ) - } - runWithoutError("QQLSActivity") { - /** - * Hook 掉一个一像素保活 [Activity] 真的我怎么都想不到讯哥的程序员做出这种事情 - * 这个东西经过测试会在锁屏的时候吊起来,解锁的时候自动 finish(),无限耍流氓耗电 - * 2022/1/25 后期查证:锁屏界面消息快速回复窗口的解锁后拉起保活界面,也是毒瘤 - */ - XposedHelpers.findAndHookMethod( - "$QQ_PACKAGE_NAME.activity.QQLSUnlockActivity", - lpparam.classLoader, - "onCreate", Bundle::class.java, - object : XC_MethodHook() { - - private var origDevice = "" - - override fun beforeHookedMethod(param: MethodHookParam?) { - /** 由于在 onCreate 里有一行判断只要型号是 xiaomi 的设备就开电源锁,所以说这里临时替换成菊花厂 */ - origDevice = Build.MANUFACTURER - if (Build.MANUFACTURER.toLowerCase(Locale.ROOT) == "xiaomi") - XposedHelpers.setStaticObjectField( - Build::class.java, - "MANUFACTURER", - "HUAWEI" - ) - } - - override fun afterHookedMethod(param: MethodHookParam?) { - (param?.thisObject as? Activity)?.finish() - /** 这里再把型号替换回去 - 不影响应用变量等 Xposed 模块修改的型号 */ - XposedHelpers.setStaticObjectField( - Build::class.java, - "MANUFACTURER", - origDevice - ) - } - } - ) - /** - * 这个东西同上 - * 反正也是一个一像素保活的 [Activity] - * 讯哥的程序员真的有你的 - * 2022/1/25 后期查证:锁屏界面消息快速回复窗口 - */ - XposedHelpers.findAndHookMethod( - "$QQ_PACKAGE_NAME.activity.QQLSActivity\$14", - lpparam.classLoader, - "run", - replaceToNull - ) - } - runWithoutError("WakerLockMonitor") { - /** - * 这个是毒瘤核心类 - * WakeLockMonitor - * 这个名字真的起的特别诗情画意 - * 带给用户的却是 shit 一样的体验 - * 里面有各种使用 Handler 和 Timer 的各种耗时常驻后台耗电办法持续接收消息 - * 直接循环全部方法全部干掉 - * 👮🏻 经过排查 Play 版本没这个类...... Emmmm 不想说啥了 - */ - lpparam.classLoader.loadClass("com.tencent.qapmsdk.qqbattery.monitor.WakeLockMonitor") - .apply { - val lockClazz = - lpparam.classLoader.loadClass("com.tencent.qapmsdk.qqbattery.monitor.WakeLockMonitor\$WakeLockEntity") - val hookClazz = - lpparam.classLoader.loadClass("com.tencent.qapmsdk.qqbattery.monitor.MethodHookParam") - val onHook = getDeclaredMethod( - "onHook", - String::class.java, - Any::class.java, - java.lang.reflect.Array.newInstance( - Any::class.java, - 0 - ).javaClass, - Any::class.java - ).apply { isAccessible = true } - val doReport = - getDeclaredMethod( - "doReport", - lockClazz, - Int::class.java - ).apply { - isAccessible = true - } - val afterHookedMethod = - getDeclaredMethod( - "afterHookedMethod", - hookClazz - ).apply { isAccessible = true } - val beforeHookedMethod = - getDeclaredMethod("beforeHookedMethod", hookClazz).apply { - isAccessible = true - } - val onAppBackground = - getDeclaredMethod("onAppBackground").apply { - isAccessible = true - } - val onOtherProcReport = - getDeclaredMethod( - "onOtherProcReport", - Bundle::class.java - ).apply { isAccessible = true } - val onProcessRun30Min = - getDeclaredMethod("onProcessRun30Min").apply { - isAccessible = true - } - val onProcessBG5Min = - getDeclaredMethod("onProcessBG5Min").apply { - isAccessible = true - } - val writeReport = - getDeclaredMethod( - "writeReport", - Boolean::class.java - ).apply { isAccessible = true } - XposedBridge.hookMethod(onHook, replaceToNull) - XposedBridge.hookMethod(doReport, replaceToNull) - XposedBridge.hookMethod(afterHookedMethod, replaceToNull) - XposedBridge.hookMethod(beforeHookedMethod, replaceToNull) - XposedBridge.hookMethod(onAppBackground, replaceToNull) - XposedBridge.hookMethod(onOtherProcReport, replaceToNull) - XposedBridge.hookMethod(onProcessRun30Min, replaceToNull) - XposedBridge.hookMethod(onProcessBG5Min, replaceToNull) - XposedBridge.hookMethod(writeReport, replaceToNull) - } - } - logD("hook Completed!") - } - } - /** 微信 */ - WECHAT_PACKAGE_NAME -> { - /** 判断是否关闭 Hook */ - if (HookMedium.getBoolean(HookMedium.DISABLE_WECHAT_HOOK)) return - lpparam.apply { - hookSystemWakeLock() - hookModuleRunningInfo() - } - // TODO 新建文件夹 - logD("ウイチャット:それが機能するかどうかはわかりませんでした") - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/tsbattery/hook/HookMedium.kt b/app/src/main/java/com/fankes/tsbattery/hook/HookMedium.kt deleted file mode 100644 index 17f73ce..0000000 --- a/app/src/main/java/com/fankes/tsbattery/hook/HookMedium.kt +++ /dev/null @@ -1,182 +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 and our eula as published - * by ferredoxin. - * - * 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 2021/11/9. - */ -@file:Suppress("DEPRECATION", "SetWorldReadable") - -package com.fankes.tsbattery.hook - -import android.content.Context -import android.content.Intent -import android.net.Uri -import android.os.Bundle -import android.os.Handler -import android.util.Log -import android.widget.Toast -import androidx.annotation.Keep -import com.fankes.tsbattery.application.TSApplication.Companion.appContext -import com.fankes.tsbattery.application.TSApplication.Companion.isMineStarted -import com.fankes.tsbattery.ui.MainActivity -import com.fankes.tsbattery.utils.FileUtils -import com.fankes.tsbattery.utils.XPrefUtils -import java.io.File - -@Keep -object HookMedium { - - const val ENABLE_HIDE_ICON = "_hide_icon" - const val ENABLE_RUN_INFO = "_tip_run_info" - const val ENABLE_QQTIM_WHITE_MODE = "_qqtim_white_mode" - const val ENABLE_QQTIM_CORESERVICE_BAN = "_qqtim_core_service_ban" - const val ENABLE_QQTIM_CORESERVICE_CHILD_BAN = "_qqtim_core_service_child_ban" - const val DISABLE_WECHAT_HOOK = "_disable_wechat_hook" - const val ENABLE_MODULE_VERSION = "_module_version" - - const val SELF_PACKAGE_NAME = "com.fankes.tsbattery" - const val QQ_PACKAGE_NAME = "com.tencent.mobileqq" - const val TIM_PACKAGE_NAME = "com.tencent.tim" - const val WECHAT_PACKAGE_NAME = "com.tencent.mm" - - /** - * 判断模块是否激活 - * 在 [HookMain] 中 Hook 掉此方法 - * @return [Boolean] 激活状态 - */ - fun isHooked(): Boolean { - Log.d("TSBattery", "isHooked: true") - return isExpModuleActive() - } - - /** - * 太极激活判断方式 - * @return [Boolean] 是否激活 - */ - private fun isExpModuleActive(): Boolean { - var isExp = false - MainActivity.instance?.also { - try { - val uri = Uri.parse("content://me.weishu.exposed.CP/") - var result: Bundle? = null - try { - result = it.contentResolver.call(uri, "active", null, null) - } catch (_: RuntimeException) { - // TaiChi is killed, try invoke - try { - val intent = Intent("me.weishu.exp.ACTION_ACTIVE") - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - it.startActivity(intent) - } catch (_: Throwable) { - return false - } - } - if (result == null) result = it.contentResolver.call(uri, "active", null, null) - if (result == null) return false - isExp = result.getBoolean("active", false) - } catch (_: Throwable) { - } - } - return isExp - } - - /** - * 获取保存的值 - * @param key 名称 - * @param default 默认值 - * @return [Boolean] 保存的值 - */ - fun getBoolean(key: String, default: Boolean = false) = - if (isMineStarted) - appContext.getSharedPreferences( - appContext.packageName + "_preferences", - Context.MODE_PRIVATE - ).getBoolean(key, default) - else XPrefUtils.getBoolean(key, default) - - /** - * 获取保存的值 - * @param key 名称 - * @param default 默认值 - * @return [String] 保存的值 - */ - fun getString(key: String, default: String = "unknown") = - if (isMineStarted) - appContext.getSharedPreferences( - appContext.packageName + "_preferences", - Context.MODE_PRIVATE - ).getString(key, default) - else XPrefUtils.getString(key, default) - - /** - * 保存值 - * @param key 名称 - * @param bool 值 - */ - fun putBoolean(key: String, bool: Boolean) { - appContext.getSharedPreferences( - appContext.packageName + "_preferences", - Context.MODE_PRIVATE - ).edit().putBoolean(key, bool).apply() - setWorldReadable(appContext) - /** 延迟继续设置强制允许 SP 可读可写 */ - Handler().postDelayed({ setWorldReadable(appContext) }, 500) - Handler().postDelayed({ setWorldReadable(appContext) }, 1000) - Handler().postDelayed({ setWorldReadable(appContext) }, 1500) - } - - /** - * 保存值 - * @param key 名称 - * @param value 值 - */ - fun putString(key: String, value: String) { - appContext.getSharedPreferences( - appContext.packageName + "_preferences", - Context.MODE_PRIVATE - ).edit().putString(key, value).apply() - setWorldReadable(appContext) - /** 延迟继续设置强制允许 SP 可读可写 */ - Handler().postDelayed({ setWorldReadable(appContext) }, 500) - Handler().postDelayed({ setWorldReadable(appContext) }, 1000) - Handler().postDelayed({ setWorldReadable(appContext) }, 1500) - } - - /** - * 强制设置 Sp 存储为全局可读可写 - * 以供模块使用 - * @param context 实例 - */ - fun setWorldReadable(context: Context) { - try { - if (FileUtils.getDefaultPrefFile(context).exists()) { - for (file in arrayOf( - FileUtils.getDataDir(context), - FileUtils.getPrefDir(context), - FileUtils.getDefaultPrefFile(context) - )) { - file.setReadable(true, false) - file.setExecutable(true, false) - } - } - } catch (_: Exception) { - Toast.makeText(context, "无法写入模块设置,请检查权限\n如果此提示一直显示,请不要双开模块", Toast.LENGTH_SHORT).show() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/tsbattery/ui/MainActivity.kt b/app/src/main/java/com/fankes/tsbattery/ui/MainActivity.kt index 1c0b87c..198bd2c 100644 --- a/app/src/main/java/com/fankes/tsbattery/ui/MainActivity.kt +++ b/app/src/main/java/com/fankes/tsbattery/ui/MainActivity.kt @@ -39,15 +39,24 @@ import androidx.constraintlayout.utils.widget.ImageFilterView import androidx.core.view.isGone import com.fankes.tsbattery.BuildConfig import com.fankes.tsbattery.R -import com.fankes.tsbattery.hook.HookMedium -import com.fankes.tsbattery.hook.HookMedium.QQ_PACKAGE_NAME -import com.fankes.tsbattery.hook.HookMedium.TIM_PACKAGE_NAME -import com.fankes.tsbattery.hook.HookMedium.WECHAT_PACKAGE_NAME +import com.fankes.tsbattery.hook.HookConst.DISABLE_WECHAT_HOOK +import com.fankes.tsbattery.hook.HookConst.ENABLE_HIDE_ICON +import com.fankes.tsbattery.hook.HookConst.ENABLE_MODULE_VERSION +import com.fankes.tsbattery.hook.HookConst.ENABLE_QQTIM_CORESERVICE_BAN +import com.fankes.tsbattery.hook.HookConst.ENABLE_QQTIM_CORESERVICE_CHILD_BAN +import com.fankes.tsbattery.hook.HookConst.ENABLE_QQTIM_WHITE_MODE +import com.fankes.tsbattery.hook.HookConst.ENABLE_RUN_INFO +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.utils.isInstall import com.fankes.tsbattery.utils.isNotSystemInDarkMode import com.fankes.tsbattery.utils.openSelfSetting import com.fankes.tsbattery.utils.showDialog import com.gyf.immersionbar.ktx.immersionBar +import com.highcapable.yukihookapi.hook.factory.isTaiChiModuleActive +import com.highcapable.yukihookapi.hook.factory.modulePrefs +import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus class MainActivity : AppCompatActivity() { @@ -58,15 +67,10 @@ class MainActivity : AppCompatActivity() { "8.2.11(Play)、8.8.17、8.8.23、8.8.35、8.8.38、8.8.50、8.8.55、8.8.68 (8.2.11、8.5.5~8.8.68)" private const val timSupportVersion = "2+、3+ (并未完全测试每个版本)" private const val wechatSupportVersion = "全版本仅支持基础省电,更多功能依然画饼" - - /** 声明当前实例 */ - var instance: MainActivity? = null } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - /** 设置自身实例 */ - instance = this setContentView(R.layout.activity_main) /** 隐藏系统的标题栏 */ supportActionBar?.hide() @@ -85,7 +89,7 @@ class MainActivity : AppCompatActivity() { findViewById(R.id.main_img_status).setImageResource(R.mipmap.succcess) findViewById(R.id.main_text_status).text = "模块已激活" /** 写入激活的模块版本 */ - putString(HookMedium.ENABLE_MODULE_VERSION, moduleVersion) + modulePrefs.putString(ENABLE_MODULE_VERSION, moduleVersion) } else showDialog { title = "模块没有激活" @@ -143,31 +147,31 @@ class MainActivity : AppCompatActivity() { val hideIconInLauncherSwitch = findViewById(R.id.hide_icon_in_launcher_switch) val notifyModuleInfoSwitch = findViewById(R.id.notify_module_info_switch) /** 获取 Sp 存储的信息 */ - qqTimProtectModeSwitch.isChecked = getBoolean(HookMedium.ENABLE_QQTIM_WHITE_MODE) - qqTimCoreServiceSwitch.isChecked = getBoolean(HookMedium.ENABLE_QQTIM_CORESERVICE_BAN) - qqTimCoreServiceKnSwitch.isChecked = getBoolean(HookMedium.ENABLE_QQTIM_CORESERVICE_CHILD_BAN) - wechatDisableHookSwitch.isChecked = getBoolean(HookMedium.DISABLE_WECHAT_HOOK) - hideIconInLauncherSwitch.isChecked = getBoolean(HookMedium.ENABLE_HIDE_ICON) - notifyModuleInfoSwitch.isChecked = getBoolean(HookMedium.ENABLE_RUN_INFO) + qqTimProtectModeSwitch.isChecked = modulePrefs.getBoolean(ENABLE_QQTIM_WHITE_MODE) + qqTimCoreServiceSwitch.isChecked = modulePrefs.getBoolean(ENABLE_QQTIM_CORESERVICE_BAN) + qqTimCoreServiceKnSwitch.isChecked = modulePrefs.getBoolean(ENABLE_QQTIM_CORESERVICE_CHILD_BAN) + wechatDisableHookSwitch.isChecked = modulePrefs.getBoolean(DISABLE_WECHAT_HOOK) + hideIconInLauncherSwitch.isChecked = modulePrefs.getBoolean(ENABLE_HIDE_ICON) + notifyModuleInfoSwitch.isChecked = modulePrefs.getBoolean(ENABLE_RUN_INFO) qqTimProtectModeSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener - putBoolean(HookMedium.ENABLE_QQTIM_WHITE_MODE, b) + modulePrefs.putBoolean(ENABLE_QQTIM_WHITE_MODE, b) } qqTimCoreServiceSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener - putBoolean(HookMedium.ENABLE_QQTIM_CORESERVICE_BAN, b) + modulePrefs.putBoolean(ENABLE_QQTIM_CORESERVICE_BAN, b) } qqTimCoreServiceKnSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener - putBoolean(HookMedium.ENABLE_QQTIM_CORESERVICE_CHILD_BAN, b) + modulePrefs.putBoolean(ENABLE_QQTIM_CORESERVICE_CHILD_BAN, b) } wechatDisableHookSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener - putBoolean(HookMedium.DISABLE_WECHAT_HOOK, b) + modulePrefs.putBoolean(DISABLE_WECHAT_HOOK, b) } hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener - putBoolean(HookMedium.ENABLE_HIDE_ICON, b) + modulePrefs.putBoolean(ENABLE_HIDE_ICON, b) packageManager.setComponentEnabledSetting( ComponentName(this@MainActivity, "com.fankes.tsbattery.Home"), if (b) PackageManager.COMPONENT_ENABLED_STATE_DISABLED else PackageManager.COMPONENT_ENABLED_STATE_ENABLED, @@ -176,7 +180,7 @@ class MainActivity : AppCompatActivity() { } notifyModuleInfoSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener - putBoolean(HookMedium.ENABLE_RUN_INFO, b) + modulePrefs.putBoolean(ENABLE_RUN_INFO, b) } /** 快捷操作 QQ */ findViewById(R.id.quick_qq_button).setOnClickListener { openSelfSetting(QQ_PACKAGE_NAME) } @@ -186,7 +190,7 @@ class MainActivity : AppCompatActivity() { findViewById(R.id.quick_wechat_button).setOnClickListener { openSelfSetting(WECHAT_PACKAGE_NAME) } /** 恰饭! */ findViewById(R.id.link_with_follow_me).setOnClickListener { - try { + runCatching { startActivity(Intent().apply { setPackage("com.coolapk.market") action = "android.intent.action.VIEW" @@ -194,20 +198,20 @@ class MainActivity : AppCompatActivity() { /** 防止顶栈一样重叠在自己的 APP 中 */ flags = Intent.FLAG_ACTIVITY_NEW_TASK }) - } catch (e: Exception) { + }.onFailure { Toast.makeText(this, "你可能没有安装酷安", Toast.LENGTH_SHORT).show() } } /** 项目地址点击事件 */ findViewById(R.id.link_with_project_address).setOnClickListener { - try { + runCatching { startActivity(Intent().apply { action = "android.intent.action.VIEW" data = Uri.parse("https://github.com/fankes/TSBattery") /** 防止顶栈一样重叠在自己的 APP 中 */ flags = Intent.FLAG_ACTIVITY_NEW_TASK }) - } catch (e: Exception) { + }.onFailure { Toast.makeText(this, "无法启动系统默认浏览器", Toast.LENGTH_SHORT).show() } } @@ -217,53 +221,5 @@ class MainActivity : AppCompatActivity() { * 判断模块是否激活 * @return [Boolean] 激活状态 */ - private fun isHooked() = HookMedium.isHooked() - - override fun onResume() { - super.onResume() - HookMedium.setWorldReadable(this) - } - - override fun onRestart() { - super.onRestart() - HookMedium.setWorldReadable(this) - } - - override fun onPause() { - super.onPause() - HookMedium.setWorldReadable(this) - } - - /** - * 获取保存的值 - * @param key 名称 - * @param default 默认值 - * @return [Boolean] 保存的值 - */ - private fun getBoolean(key: String, default: Boolean = false) = HookMedium.getBoolean(key, default) - - /** - * 保存值 - * @param key 名称 - * @param bool 值 - */ - private fun putBoolean(key: String, bool: Boolean) = HookMedium.putBoolean(key, bool) - - /** - * 保存值 - * @param key 名称 - * @param value 值 - */ - private fun putString(key: String, value: String) = HookMedium.putString(key, value) - - override fun onBackPressed() { - HookMedium.setWorldReadable(this) - super.onBackPressed() - } - - override fun onDestroy() { - super.onDestroy() - /** 销毁实例防止内存泄漏 */ - instance = null - } + private fun isHooked() = YukiHookModuleStatus.isActive() || isTaiChiModuleActive } \ No newline at end of file diff --git a/app/src/main/java/com/fankes/tsbattery/utils/FileUtils.java b/app/src/main/java/com/fankes/tsbattery/utils/FileUtils.java deleted file mode 100755 index f8c9122..0000000 --- a/app/src/main/java/com/fankes/tsbattery/utils/FileUtils.java +++ /dev/null @@ -1,87 +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 and our eula as published - * by ferredoxin. - * - * 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 zpp0196 on 2019/2/9. - */ -package com.fankes.tsbattery.utils; - -import android.content.Context; - -import com.fankes.tsbattery.BuildConfig; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; - -@SuppressWarnings("ALL") -public class FileUtils { - - private static final String FILE_PREF_NAME = BuildConfig.APPLICATION_ID + "_preferences.xml"; - - public static boolean copyFile(File srcFile, File targetFile) { - FileInputStream ins = null; - FileOutputStream out = null; - try { - if (targetFile.exists()) { - targetFile.delete(); - } - File targetParent = targetFile.getParentFile(); - if (!targetParent.exists()) { - targetParent.mkdirs(); - } - targetFile.createNewFile(); - ins = new FileInputStream(srcFile); - out = new FileOutputStream(targetFile); - byte[] b = new byte[1024]; - int n; - while ((n = ins.read(b)) != -1) { - out.write(b, 0, n); - } - } catch (IOException e) { - e.printStackTrace(); - return false; - } finally { - try { - if (ins != null) { - ins.close(); - } - if (out != null) { - out.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - return true; - } - - public static File getDataDir(Context context) { - return new File(context.getApplicationInfo().dataDir); - } - - public static File getPrefDir(Context context) { - return new File(getDataDir(context), "shared_prefs"); - } - - public static File getDefaultPrefFile(Context context) { - return new File(getPrefDir(context), FILE_PREF_NAME); - } -} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index f996f63..4e508ff 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -775,6 +775,20 @@ android:textColor="@color/colorTextGray" android:textSize="16sp" /> + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 185268c..eadba67 100644 --- a/build.gradle +++ b/build.gradle @@ -1,30 +1,12 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. -buildscript { - ext.kotlin_version = "1.6.10" - repositories { - google() - maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' } - maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' } - maven { url "https://www.jitpack.io" } - mavenCentral() - } - dependencies { - classpath "com.android.tools.build:gradle:7.0.4" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files - } +plugins { + id 'com.android.application' version '7.1.1' apply false + id 'com.android.library' version '7.1.1' apply false + id 'org.jetbrains.kotlin.android' version '1.6.10' apply false } -allprojects { - repositories { - google() - maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' } - maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' } - maven { url "https://www.jitpack.io" } - mavenCentral() - } +ext { + appVersionName = "3.1" + appVersionCode = 10 } task clean(type: Delete) { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 05df554..caaf728 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Sep 04 04:05:23 CST 2021 +#Mon Feb 14 23:27:58 CST 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle b/settings.gradle index f45aff5..222ae02 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,17 @@ +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + maven { url "https://api.xposed.info/" } + mavenCentral() + } +} rootProject.name = "TSBattery" include ':app'