16 Commits
normal ... 2.5

Author SHA1 Message Date
163eecc0b8 Update Version to 2.5 Support WeChat(Base Save Battery) 2021-12-22 01:12:54 +08:00
5707a8b394 微信省电(新建文件夹) 2021-12-22 01:07:16 +08:00
11aa064b90 微信省电(新建文件夹) 2021-12-22 00:36:02 +08:00
15b1bfa498 合并优化代码,优化模块主界面显示效果,加入微信省电的未来计划(新建文件夹) 2021-12-22 00:28:23 +08:00
Fankesyooni
673dc8127b Merge pull request #6 from StarWishsama/master
支持 QQ 8.8.35
2021-12-21 23:44:51 +08:00
StarWishsama
7d9c360c9d feat: support QQ 8.8.35 2021-12-21 22:58:50 +08:00
d5e885db6c 更新了一个日志外显功能,并将 API 降回 26 2021-12-07 00:30:31 +08:00
7481bbd7ef Update README 2021-11-28 13:29:45 +08:00
23c94e4e26 Support QQ 8.8.50 Update version to 2.4 2021-11-27 19:18:24 +08:00
ead73eb23d Support QQ 8.8.50 Update version to 2.4 2021-11-27 19:16:50 +08:00
5d82e9a104 整理代码并更新依赖版本 2021-11-27 18:59:44 +08:00
Fankesyooni
14d1b9c5ac Merge pull request #4 from JiZhi-Error/master
Support QQ 8.8.50
2021-11-24 18:52:10 +08:00
JiZhi
44ea28edda Change the key file path to a relative path
Support QQ 8.8.50
2021-11-20 22:38:12 +08:00
b075fd8424 Update to 2.3 support QQ 8.8.38 2021-11-09 19:55:52 +08:00
4395f31895 Update to 2.3 support QQ 8.8.38 2021-11-09 19:37:22 +08:00
a25d2b814b Update to 2.3 support QQ 8.8.38 2021-11-09 19:37:01 +08:00
21 changed files with 725 additions and 282 deletions

3
.idea/gradle.xml generated
View File

@@ -4,9 +4,10 @@
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="testRunner" value="PLATFORM" /> <option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" /> <option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="11" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />

View File

@@ -26,5 +26,20 @@
<option name="name" value="Google" /> <option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" /> <option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository> </remote-repository>
<remote-repository>
<option name="id" value="maven3" />
<option name="name" value="maven3" />
<option name="url" value="https://www.jitpack.io" />
</remote-repository>
<remote-repository>
<option name="id" value="maven" />
<option name="name" value="maven" />
<option name="url" value="https://maven.aliyun.com/nexus/content/groups/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven2" />
<option name="name" value="maven2" />
<option name="url" value="https://maven.aliyun.com/nexus/content/repositories/jcenter" />
</remote-repository>
</component> </component>
</project> </project>

9
.idea/misc.xml generated
View File

@@ -1,6 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="app/src/main/res/layout/activity_main.xml" value="0.375" />
</map>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
</set>
</option>
</component>
</project>

47
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,47 @@
# 开始贡献
欢迎为此项目进行新版本的适配代码贡献!<br/>
## 分支规定
不管是直接 Push 代码还是提交 Pull Request都必须使 commit 指向 master 分支。
## 代码格式规范
- 1.全部提交代码必须使用 IDE(Android Studio 或 IDEA) 进行格式化,未经格式化的代码将拒绝合并提交请求
- 2.代码必须使用 4 spaces 缩进格式化
## 代码注释规范
- 1.第一种注释方式:可使用在方法名或顶级变量名上
```kotlin
/** 注释内容 */
fun a() {
}
/**
* 注释名称
* @param test 方法名称
* @return 返回值名称
*/
fun a(test: String) {
}
```
- 2.第二种注释方式:仅可使用在变量后方
```kotlin
val a = "" // 变量注释
```
- ⚠️注意:只允许两个 // 后方要有空格
## 项目要求
- 1.调试性质或大批量注释代码,禁止提交
- 2.类名和方法名仅能由开发者进行修改和提交,禁止随意修改项目名称、方法名称以及类名
- 3.禁止随意更新项目依赖以及增加新的依赖,有问题请提前提交到 issues 进行说明
- 4.禁止更新项目版本号,版本号交由开发者合并代码并发布 release 版本
- 5.代码语言要求,请统一使用 Kotlin除特殊情况外不接受其他语言的提交
- 6.以上

View File

@@ -1,7 +1,43 @@
# TSBattery # TSBattery
![Eclipse Marketplace](https://img.shields.io/badge/build-passing-brightgreen)
![Eclipse Marketplace](https://img.shields.io/badge/license-GPL3.0-blue)
![Eclipse Marketplace](https://img.shields.io/badge/version-v2.5-green)
<br/><br/>
TSBattery a new way to save your battery avoid cancer apps hacker it.<br/> TSBattery a new way to save your battery avoid cancer apps hacker it.<br/>
TSBattery 是一个旨在使 QQ、TIM 变得更省电的开源 Xposed 模块 TSBattery 是一个旨在使 QQ、TIM、微信 变得更省电的开源 Xposed 模块
# Get startted
此模块支持原生 Xposed、Lsposed(作用域 QQ、TIM 如果不起作用勾选系统框架)、EdXposed(不推荐)、太极无极(阴和阳)、Pine(梦境模块) # 开始使用
点击下载最新版本
<a href='https://github.com/fankes/TSBattery/releases'>![Eclipse Marketplace](https://img.shields.io/badge/download-v2.5-green)</a>
<br/><br/>
⚠️适配说明:此模块支持原生 Xposed、Lsposed(作用域 QQ、TIM、微信 如果不起作用勾选系统框架)、EdXposed(不推荐)、太极无极(阴和阳)、Pine(梦境模块)
# 禁止任何商业用途 # 禁止任何商业用途
本模块完全开源免费,如果好用你可以打赏支持开发,严禁未经许可进行二改贩卖,违者必惩必究。 本模块完全开源免费,如果好用你可以打赏支持开发,严禁未经许可进行二改贩卖,违者必惩必究。
# 开始贡献
欢迎为此项目进行新版本的适配代码贡献!<br/>
- [CONTRIBUTING](https://github.com/fankes/TSBattery/blob/master/CONTRIBUTING.md)
# 许可证
- [GPL-3.0](https://www.gnu.org/licenses/gpl-3.0.html)
```
Copyright (C) 2020-2021 Fankes Studio(qzmmcn@163.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
```

View File

@@ -6,7 +6,7 @@ plugins {
android { android {
signingConfigs { signingConfigs {
debug { debug {
storeFile file('/Users/fankes/ProjectPath/AndroidStudioProjects/TSBattery/keystore/public') storeFile file('../keystore/public')
storePassword '123456' storePassword '123456'
keyAlias 'public' keyAlias 'public'
keyPassword '123456' keyPassword '123456'
@@ -14,16 +14,16 @@ android {
v2SigningEnabled true v2SigningEnabled true
} }
} }
compileSdkVersion 30 compileSdkVersion 31
buildToolsVersion "30.0.3" buildToolsVersion "31.0.0"
defaultConfig { defaultConfig {
applicationId "com.fankes.tsbattery" applicationId "com.fankes.tsbattery"
minSdkVersion 22 minSdkVersion 22
//noinspection ExpiredTargetSdkVersion,OldTargetApi //noinspection ExpiredTargetSdkVersion
targetSdkVersion 26 targetSdkVersion 26
versionCode 5 versionCode 8
versionName "2.2" versionName "2.5"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
@@ -46,25 +46,18 @@ android {
dependencies { dependencies {
compileOnly 'de.robv.android.xposed:api:82' compileOnly 'de.robv.android.xposed:api:82'
// 基础依赖包,必须要依赖 // 基础依赖包
implementation 'com.gyf.immersionbar:immersionbar:3.0.0' implementation 'com.gyf.immersionbar:immersionbar:3.0.0'
// fragment快速实现(可选) // Fragment 快速实现
implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0'
// kotlin扩展(可选) // Kotlin 扩展
implementation 'com.gyf.immersionbar:immersionbar-ktx:3.0.0' implementation 'com.gyf.immersionbar:immersionbar-ktx:3.0.0'
//noinspection GradleDependency,DifferentStdlibGradleVersion
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
//noinspection GradleDependency implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.core:core-ktx:1.5.0' implementation 'androidx.appcompat:appcompat:1.4.0'
//noinspection GradleDependency implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
//noinspection GradleDependency
implementation 'com.google.android.material:material:1.3.0'
//noinspection GradleDependency
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
//noinspection GradleDependency androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
//noinspection GradleDependency
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
} }

Binary file not shown.

View File

@@ -1,18 +1,20 @@
{ {
"version": 2, "version": 3,
"artifactType": { "artifactType": {
"type": "APK", "type": "APK",
"kind": "Directory" "kind": "Directory"
}, },
"applicationId": "com.fankes.tsbattery", "applicationId": "com.fankes.tsbattery",
"variantName": "processReleaseResources", "variantName": "release",
"elements": [ "elements": [
{ {
"type": "SINGLE", "type": "SINGLE",
"filters": [], "filters": [],
"versionCode": 5, "attributes": [],
"versionName": "2.2", "versionCode": 7,
"versionName": "2.4",
"outputFile": "app-release.apk" "outputFile": "app-release.apk"
} }
] ],
"elementType": "File"
} }

View File

@@ -4,6 +4,7 @@
package="com.fankes.tsbattery"> package="com.fankes.tsbattery">
<application <application
android:name=".application.TSApplication"
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
@@ -20,7 +21,7 @@
<!-- 模块描述 --> <!-- 模块描述 -->
<meta-data <meta-data
android:name="xposeddescription" android:name="xposeddescription"
android:value="抵制毒瘤拒绝疯狂耗电Tencent 社交毒瘤一键省电模块(目前支持 QQ、TIM)通过干掉电源锁常驻减少电量消耗理论支持最新版本。by 酷安 @星夜不荟" /> android:value="抵制毒瘤拒绝疯狂耗电Tencent 社交毒瘤一键省电模块(目前支持 QQ、TIM、微信)通过干掉电源锁常驻减少电量消耗理论支持最新版本。by 酷安 @星夜不荟" />
<!-- 最低xposed版本号 --> <!-- 最低xposed版本号 -->
<meta-data <meta-data
@@ -28,8 +29,11 @@
android:value="82" /> android:value="82" />
<activity <activity
android:name="com.fankes.tsbattery.MainActivity" android:name="com.fankes.tsbattery.ui.MainActivity"
android:label="@string/app_name"> android:exported="true"
android:label="@string/app_name"
android:screenOrientation="behind">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="de.robv.android.xposed.category.MODULE_SETTINGS" /> <category android:name="de.robv.android.xposed.category.MODULE_SETTINGS" />
@@ -39,8 +43,11 @@
<activity-alias <activity-alias
android:name="com.fankes.tsbattery.Home" android:name="com.fankes.tsbattery.Home"
android:enabled="true" android:enabled="true"
android:exported="true"
android:label="@string/app_name" android:label="@string/app_name"
android:targetActivity="com.fankes.tsbattery.MainActivity"> android:screenOrientation="behind"
android:targetActivity="com.fankes.tsbattery.ui.MainActivity">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2021. Fankes Studio(qzmmcn@163.com)
*
* This file is part of TSBattery.
*
* TSBattery is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* TSBattery 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* This file is Created by fankes on 2021/11/9.
*/
@file:Suppress("unused")
package com.fankes.tsbattery.application
import android.app.Application
import androidx.appcompat.app.AppCompatDelegate
class TSApplication : Application() {
override fun onCreate() {
super.onCreate()
/** 禁止系统夜间模式对自己造成干扰 - 模块要什么夜间模式?😅 (其实是我懒) */
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
}
}

View File

@@ -1,4 +1,4 @@
/* /**
* Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com)
* *
* This file is part of TSBattery. * This file is part of TSBattery.
@@ -36,12 +36,96 @@ import java.util.*
@Keep @Keep
class HookMain : IXposedHookLoadPackage { class HookMain : IXposedHookLoadPackage {
companion object {
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(
"com.tencent.mobileqq.$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.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")
}
else -> logD("$version not supported!")
}
}
/** /**
* Print the log * Print the log
* @param content * @param content
*/ */
private fun logD(content: String) { private fun logD(content: String) {
XposedBridge.log(content) XposedBridge.log("[TSBattery][D]>$content")
Log.d("TSBattery", content) Log.d("TSBattery", content)
} }
@@ -50,56 +134,67 @@ class HookMain : IXposedHookLoadPackage {
* @param content * @param content
*/ */
private fun logE(content: String, e: Throwable? = null) { private fun logE(content: String, e: Throwable? = null) {
XposedBridge.log(content) XposedBridge.log("[TSBattery][E]>$content")
XposedBridge.log(e)
Log.e("TSBattery", content, 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
)
}
}
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam?) { override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam?) {
if (lpparam == null) return if (lpparam == null) return
when (lpparam.packageName) { when (lpparam.packageName) {
/*Hook 自身*/ /** Hook 自身 */
"com.fankes.tsbattery" -> "com.fankes.tsbattery" ->
XposedHelpers.findAndHookMethod( XposedHelpers.findAndHookMethod(
"com.fankes.tsbattery.MainActivity", "com.fankes.tsbattery.hook.HookMedium",
lpparam.classLoader, lpparam.classLoader,
"isHooked", "isHooked",
object : XC_MethodReplacement() { replaceToTrue
override fun replaceHookedMethod(param: MethodHookParam?): Any { )
return true /** 经过测试 QQ 与 TIM 这两个是一个模子里面的东西,所以他们的类名也基本上是一样的 */
}
})
/*经过测试 QQ 与 TIM 这两个是一个模子里面的东西,所以他们的类名也基本上是一样的*/
"com.tencent.mobileqq", "com.tencent.tim" -> { "com.tencent.mobileqq", "com.tencent.tim" -> {
try { lpparam.hookSystemWakeLock()
/** 增加通知栏文本显示守护状态 */
runWithoutError("Notification") {
XposedHelpers.findAndHookMethod( XposedHelpers.findAndHookMethod(
"android.os.PowerManager\$WakeLock", "android.app.Notification\$Builder",
lpparam.classLoader, lpparam.classLoader,
"acquire", "setContentText",
object : XC_MethodReplacement() { CharSequence::class.java,
override fun replaceHookedMethod(param: MethodHookParam?): Any? { object : XC_MethodHook() {
return null 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 守护中")
}
} }
}) })
} catch (e: Throwable) {
logE("handleLoadPackage: hook wakeLock acquire() Failed", e)
} }
try { /** 判断是否开启提示模块运行信息 */
XposedHelpers.findAndHookMethod( if (XPrefUtils.getBoolean(HookMedium.ENABLE_RUN_INFO))
"android.os.PowerManager\$WakeLock", runWithoutError("SplashActivity") {
lpparam.classLoader,
"acquire",
Long::class.java,
object : XC_MethodReplacement() {
override fun replaceHookedMethod(param: MethodHookParam?): Any? {
return null
}
})
} catch (e: Throwable) {
logE("handleLoadPackage: hook wakeLock acquire(time) Failed", e)
}
/*判断是否开启提示模块运行信息*/
if (XPrefUtils.getBoolean("_tip_run_info"))
try {
/** /**
* Hook 启动界面的第一个 [Activity] * Hook 启动界面的第一个 [Activity]
* QQ 和 TIM 都是一样的类 * QQ 和 TIM 都是一样的类
@@ -113,56 +208,42 @@ class HookMain : IXposedHookLoadPackage {
object : XC_MethodHook() { object : XC_MethodHook() {
override fun afterHookedMethod(param: MethodHookParam?) { override fun afterHookedMethod(param: MethodHookParam?) {
val self = param!!.thisObject as Activity val self = param?.thisObject as? Activity ?: return
AlertDialog.Builder( runWithoutError("模块已激活,但显示信息弹窗失败了") {
self, AlertDialog.Builder(
android.R.style.Theme_Material_Dialog_Alert self,
).setCancelable(false) android.R.style.Theme_Material_Light_Dialog
.setTitle("TSBattery 已激活") ).setCancelable(false)
.setMessage( .setTitle("TSBattery 已激活")
"模块工作看起来一切正常,请自行测试是否能达到省电效果。\n\n" + .setMessage(
"当前模式:${if (XPrefUtils.getBoolean("_white_mode")) "保守模式" else "完全模式"}" + "[提示模块运行信息功能已打开]\n" +
"\n\n包名:${self.packageName}\n版本:${ "模块工作看起来一切正常,请自行测试是否能达到省电效果。\n\n" +
self.packageManager.getPackageInfo( "已生效模块版本:${XPrefUtils.getString(HookMedium.ENABLE_MODULE_VERSION)}\n" +
self.packageName, "当前模式:${if (XPrefUtils.getBoolean(HookMedium.ENABLE_WHITE_MODE)) "保守模式" else "完全模式"}" +
0 "\n\n包名:${self.packageName}\n版本:${
).versionName self.packageManager.getPackageInfo(
}(${ self.packageName,
self.packageManager.getPackageInfo( 0
self.packageName, ).versionName
0 }(${
).versionCode self.packageManager.getPackageInfo(
})" + "\n\nPS模块只对挂后台锁屏情况下有省电效果请不要将过多的群提醒消息通知打开这样子在使用过程时照样会极其耗电\n" + self.packageName,
"如果你不想看到此提示。请在模块设置中关闭运行信息提醒,此设置默认关闭。\n" + 0
"开发者 酷安 @星夜不荟\n未经允许禁止转载、修改或复制我的劳动成果。" ).versionCode
) })" + "\n\nPS模块只对挂后台锁屏情况下有省电效果请不要将过多的群提醒消息通知打开这样子在使用过程时照样会极其耗电\n" +
.setPositiveButton("我知道了", null) "如果你不想看到此提示。请在模块设置中关闭“提示模块运行信息”,此设置默认关闭。\n" +
.show() "开发者 酷安 @星夜不荟\n未经允许禁止转载、修改或复制我的劳动成果。"
)
.setPositiveButton("我知道了", null)
.show()
}
} }
}) })
} catch (e: Exception) {
logE("handleLoadPackage: hook SplashActivity Failed", e)
} }
/*关闭保守模式后不再仅仅作用于系统电源锁*/ /** 关闭保守模式后不再仅仅作用于系统电源锁 */
if (!XPrefUtils.getBoolean("_white_mode")) { if (!XPrefUtils.getBoolean(HookMedium.ENABLE_WHITE_MODE)) {
val replaceMent = object : XC_MethodReplacement() { runWithoutError("BaseChatPie(first time)") {
override fun replaceHookedMethod(param: MethodHookParam?): Any? { /** 通过在 SplashActivity 里取到应用的版本号 */
return null
}
}
/**
* 这个类 BaseChatPie 是控制聊天界面的
* 里面有两个随机混淆的方法
* 这两个方法一个是挂起电源锁常驻亮屏
* 一个是停止常驻亮屏
* 不由分说每个版本混淆的方法名都会变
* 所以说每个版本重新适配 - 也可以提交分支帮我适配
* 8.8.17 版本是 bd be
* 8.8.23 版本是 bf bg
* ⚠️ Hook 错了方法会造成闪退!
*/
try {
/*通过在 SplashActivity 里取到应用的版本号*/
XposedHelpers.findAndHookMethod( XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.SplashActivity", "com.tencent.mobileqq.activity.SplashActivity",
lpparam.classLoader, lpparam.classLoader,
@@ -171,54 +252,19 @@ class HookMain : IXposedHookLoadPackage {
object : XC_MethodHook() { object : XC_MethodHook() {
override fun beforeHookedMethod(param: MethodHookParam?) { override fun beforeHookedMethod(param: MethodHookParam?) {
val self = param!!.thisObject as Activity val self = param?.thisObject as? Activity ?: return
val name = self.packageName val name = self.packageName
val version = val version =
self.packageManager.getPackageInfo(name, 0).versionName self.packageManager.getPackageInfo(name, 0).versionName
/*这个地方我们只处理 QQ*/ /** 这个地方我们只处理 QQ */
try { runWithoutError("BaseChatPie") {
if (name == "com.tencent.mobileqq") { if (name == "com.tencent.mobileqq")
when (version) { lpparam.hookQQBaseChatPie(version)
"8.8.17" -> {
XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.aio.core.BaseChatPie",
lpparam.classLoader,
"bd",
replaceMent
)
XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.aio.core.BaseChatPie",
lpparam.classLoader,
"be",
replaceMent
)
}
"8.8.23" -> {
XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.aio.core.BaseChatPie",
lpparam.classLoader,
"bf",
replaceMent
)
XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.aio.core.BaseChatPie",
lpparam.classLoader,
"bg",
replaceMent
)
}
//TODO 后面的版本逐个适配 此方法没封装 目前比较笨蛋 主要是我懒得写
}
}
} catch (e: Exception) {
logE("handleLoadPackage: hook BaseChatPie Failed", e)
} }
} }
}) })
} catch (e: Exception) {
logE("handleLoadPackage: hook BaseChatPie(first time) Failed", e)
} }
try { runWithoutError("WakerLock") {
/** /**
* 一个不知道是什么作用的电源锁 * 一个不知道是什么作用的电源锁
* 同样直接干掉 * 同样直接干掉
@@ -227,12 +273,10 @@ class HookMain : IXposedHookLoadPackage {
"com.tencent.mars.ilink.comm.WakerLock", "com.tencent.mars.ilink.comm.WakerLock",
lpparam.classLoader, lpparam.classLoader,
"lock", Long::class.java, "lock", Long::class.java,
replaceMent replaceToNull
) )
} catch (e: Exception) {
logE("handleLoadPackage: hook WakerLock Failed", e)
} }
try { runWithoutError("QQLSActivity") {
/** /**
* Hook 掉一个一像素保活 [Activity] 真的我怎么都想不到讯哥的程序员做出这种事情 * Hook 掉一个一像素保活 [Activity] 真的我怎么都想不到讯哥的程序员做出这种事情
* 这个东西经过测试会在锁屏的时候吊起来,解锁的时候自动 finish(),无限耍流氓耗电 * 这个东西经过测试会在锁屏的时候吊起来,解锁的时候自动 finish(),无限耍流氓耗电
@@ -246,7 +290,7 @@ class HookMain : IXposedHookLoadPackage {
private var origDevice = "" private var origDevice = ""
override fun beforeHookedMethod(param: MethodHookParam?) { override fun beforeHookedMethod(param: MethodHookParam?) {
/*由于在 onCreate 里有一行判断只要型号是 xiaomi 的设备就开电源锁,所以说这里临时替换成菊花厂*/ /** 由于在 onCreate 里有一行判断只要型号是 xiaomi 的设备就开电源锁,所以说这里临时替换成菊花厂 */
origDevice = Build.MANUFACTURER origDevice = Build.MANUFACTURER
if (Build.MANUFACTURER.toLowerCase(Locale.ROOT) == "xiaomi") if (Build.MANUFACTURER.toLowerCase(Locale.ROOT) == "xiaomi")
XposedHelpers.setStaticObjectField( XposedHelpers.setStaticObjectField(
@@ -258,7 +302,7 @@ class HookMain : IXposedHookLoadPackage {
override fun afterHookedMethod(param: MethodHookParam?) { override fun afterHookedMethod(param: MethodHookParam?) {
(param?.thisObject as? Activity)?.finish() (param?.thisObject as? Activity)?.finish()
/*这里再把型号替换回去 - 不影响应用变量等 Xposed 模块修改的型号*/ /** 这里再把型号替换回去 - 不影响应用变量等 Xposed 模块修改的型号 */
XposedHelpers.setStaticObjectField( XposedHelpers.setStaticObjectField(
Build::class.java, Build::class.java,
"MANUFACTURER", "MANUFACTURER",
@@ -276,12 +320,10 @@ class HookMain : IXposedHookLoadPackage {
"com.tencent.mobileqq.activity.QQLSActivity\$14", "com.tencent.mobileqq.activity.QQLSActivity\$14",
lpparam.classLoader, lpparam.classLoader,
"run", "run",
replaceMent replaceToNull
) )
} catch (e: Exception) {
logE("handleLoadPackage: hook QQLSActivity Failed", e)
} }
try { runWithoutError("WakerLockMonitor") {
/** /**
* 这个是毒瘤核心类 * 这个是毒瘤核心类
* WakeLockMonitor * WakeLockMonitor
@@ -345,22 +387,71 @@ class HookMain : IXposedHookLoadPackage {
"writeReport", "writeReport",
Boolean::class.java Boolean::class.java
).apply { isAccessible = true } ).apply { isAccessible = true }
XposedBridge.hookMethod(onHook, replaceMent) XposedBridge.hookMethod(onHook, replaceToNull)
XposedBridge.hookMethod(doReport, replaceMent) XposedBridge.hookMethod(doReport, replaceToNull)
XposedBridge.hookMethod(afterHookedMethod, replaceMent) XposedBridge.hookMethod(afterHookedMethod, replaceToNull)
XposedBridge.hookMethod(beforeHookedMethod, replaceMent) XposedBridge.hookMethod(beforeHookedMethod, replaceToNull)
XposedBridge.hookMethod(onAppBackground, replaceMent) XposedBridge.hookMethod(onAppBackground, replaceToNull)
XposedBridge.hookMethod(onOtherProcReport, replaceMent) XposedBridge.hookMethod(onOtherProcReport, replaceToNull)
XposedBridge.hookMethod(onProcessRun30Min, replaceMent) XposedBridge.hookMethod(onProcessRun30Min, replaceToNull)
XposedBridge.hookMethod(onProcessBG5Min, replaceMent) XposedBridge.hookMethod(onProcessBG5Min, replaceToNull)
XposedBridge.hookMethod(writeReport, replaceMent) XposedBridge.hookMethod(writeReport, replaceToNull)
} }
} catch (e: Throwable) {
logE("handleLoadPackage: hook WakerLockMonitor Failed", e)
} }
logD("handleLoadPackage: hook Complete!") logD("hook Completed!")
} }
} }
/** 微信 */
"com.tencent.mm" -> {
lpparam.hookSystemWakeLock()
/** 判断是否开启提示模块运行信息 */
if (XPrefUtils.getBoolean(HookMedium.ENABLE_RUN_INFO))
runWithoutError("LauncherUI") {
/**
* Hook 启动界面的第一个 [Activity]
* 在里面加入提示运行信息的对话框测试模块是否激活
*/
XposedHelpers.findAndHookMethod(
"com.tencent.mm.ui.LauncherUI",
lpparam.classLoader,
"onCreate",
Bundle::class.java,
object : XC_MethodHook() {
override fun afterHookedMethod(param: MethodHookParam?) {
val self = param?.thisObject as? Activity ?: return
runWithoutError("模块已激活,但显示信息弹窗失败了") {
AlertDialog.Builder(
self,
android.R.style.Theme_Material_Light_Dialog
).setCancelable(false)
.setTitle("TSBattery 已激活")
.setMessage(
"[提示模块运行信息功能已打开]\n" +
"模块工作看起来一切正常,请自行测试是否能达到省电效果。\n\n" +
"已生效模块版本:${XPrefUtils.getString(HookMedium.ENABLE_MODULE_VERSION)}\n" +
"当前模式:基础省电" +
"\n\n包名:${self.packageName}\n版本:${
self.packageManager.getPackageInfo(
self.packageName,
0
).versionName
}(${
self.packageManager.getPackageInfo(
self.packageName,
0
).versionCode
})" + "\n\nPS当前只支持微信的基础省电即系统电源锁后续会继续适配微信相关的省电功能(在新建文件夹了)"
)
.setPositiveButton("我知道了", null)
.show()
}
}
})
}
// TODO 新建文件夹
logD("それが機能するかどうかはわかりません")
}
} }
} }
} }

View File

@@ -0,0 +1,79 @@
/*
* Copyright (C) 2021. Fankes Studio(qzmmcn@163.com)
*
* This file is part of TSBattery.
*
* TSBattery is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* TSBattery 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* This file is Created by fankes on 2021/11/9.
*/
package com.fankes.tsbattery.hook
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import androidx.annotation.Keep
import com.fankes.tsbattery.ui.MainActivity
@Keep
object HookMedium {
const val ENABLE_HIDE_ICON = "_hide_icon"
const val ENABLE_RUN_INFO = "_tip_run_info"
const val ENABLE_WHITE_MODE = "_white_mode"
const val ENABLE_MODULE_VERSION = "_module_version"
/**
* 判断模块是否激活
* 在 [HookMain] 中 Hook 掉此方法
* @return 激活状态
*/
fun isHooked(): Boolean {
Log.d("TSBattery", "isHooked: true")
return isExpModuleActive()
}
/**
* 新增太极判断方式
* @return 是否激活
*/
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 (e: 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 (e1: 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 (ignored: Throwable) {
}
}
return isExp
}
}

View File

@@ -1,4 +1,4 @@
/* /**
* Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com)
* *
* This file is part of TSBattery. * This file is part of TSBattery.
@@ -20,10 +20,10 @@
*/ */
@file:Suppress( @file:Suppress(
"DEPRECATION", "SetTextI18n", "SetWorldReadable", "WorldReadableFiles", "DEPRECATION", "SetTextI18n", "SetWorldReadable", "WorldReadableFiles",
"LocalVariableName" "LocalVariableName", "SameParameterValue"
) )
package com.fankes.tsbattery package com.fankes.tsbattery.ui
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
@@ -32,80 +32,116 @@ import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.util.Log
import android.view.View import android.view.View
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.annotation.Keep
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.widget.SwitchCompat import androidx.appcompat.widget.SwitchCompat
import androidx.constraintlayout.utils.widget.ImageFilterView import androidx.constraintlayout.utils.widget.ImageFilterView
import com.fankes.tsbattery.hook.HookMain import com.fankes.tsbattery.BuildConfig
import com.fankes.tsbattery.R
import com.fankes.tsbattery.hook.HookMedium
import com.fankes.tsbattery.utils.FileUtils import com.fankes.tsbattery.utils.FileUtils
import com.gyf.immersionbar.ImmersionBar import com.gyf.immersionbar.ImmersionBar
import java.io.File import java.io.File
@Keep
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
companion object { companion object {
private const val moduleVersion = BuildConfig.VERSION_NAME private const val moduleVersion = BuildConfig.VERSION_NAME
private const val moduleSupport = "QQ 8.5.5~8.8.23、TIM 2+" private const val qqSupportVersion = "8.8.17、8.8.23、8.8.35、8.8.38、8.8.50 (8.5.5~8.8.50)"
private const val timSupportVersion = "2+、3+ (并未完全测试每个版本)"
private const val wechatSupportVersion = "全版本仅支持基础省电,更多功能敬请期待"
/** 声明当前实例 */
var instance: MainActivity? = null
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
/** 设置自身实例 */
instance = this
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
/*禁止系统夜间模式对自己造成干扰*/ /** 隐藏系统的标题栏 */
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
/*隐藏系统的标题栏*/
supportActionBar?.hide() supportActionBar?.hide()
/*初始化沉浸状态栏*/ /** 初始化沉浸状态栏 */
ImmersionBar.with(this) ImmersionBar.with(this)
.statusBarColor("#FFFFFFFF") .statusBarColor(R.color.white)
.autoDarkModeEnable(false) .autoDarkModeEnable(false)
.statusBarDarkFont(true) .statusBarDarkFont(true)
.navigationBarColor("#FFFFFFFF") .navigationBarColor(R.color.white)
.navigationBarDarkIcon(true) .navigationBarDarkIcon(true)
.fitsSystemWindows(true) .fitsSystemWindows(true)
.init() .init()
/*判断 Hook 状态*/ /** 判断 Hook 状态 */
if (isHooked()) { if (isHooked()) {
findViewById<LinearLayout>(R.id.main_lin_status).setBackgroundResource(R.drawable.green_round) findViewById<LinearLayout>(R.id.main_lin_status).setBackgroundResource(R.drawable.green_round)
findViewById<ImageFilterView>(R.id.main_img_status).setImageResource(R.mipmap.succcess) findViewById<ImageFilterView>(R.id.main_img_status).setImageResource(R.mipmap.succcess)
findViewById<TextView>(R.id.main_text_status).text = "模块已激活" findViewById<TextView>(R.id.main_text_status).text = "模块已激活"
/** 写入激活的模块版本 */
putString(HookMedium.ENABLE_MODULE_VERSION, moduleVersion)
} else } else
AlertDialog.Builder(this) AlertDialog.Builder(this)
.setTitle("模块没有激活") .setTitle("模块没有激活")
.setMessage( .setMessage(
"检测到模块没有激活,模块需要 Xposed 环境依赖,同时需要系统拥有 Root 权限(太极阴可以免 Root),请自行查看本页面使用帮助与说明第三条。\n" + "检测到模块没有激活,模块需要 Xposed 环境依赖,同时需要系统拥有 Root 权限(太极阴可以免 Root),请自行查看本页面使用帮助与说明第三条。\n" +
"太极、应用转生、梦境(Pine)和第三方 Xposed 激活后可能不会提示激活,若想验证是否激活请打开“提示模块运行信息”自行检查,如果生效就代表模块运行正常,这里的激活状态只是一个显示意义上的存在。" "太极、应用转生、梦境(Pine)和第三方 Xposed 激活后可能不会提示激活,若想验证是否激活请打开“提示模块运行信息”自行检查," +
"如果生效就代表模块运行正常,这里的激活状态只是一个显示意义上的存在。\n" +
"太极(无极)在 MIUI 设备上会提示打开授权,请进行允许,然后再次打开本应用查看激活状态。"
) )
.setPositiveButton("我知道了", null) .setPositiveButton("我知道了", null)
.setCancelable(false) .setCancelable(false)
.show() .show()
/*设置文本*/ /** 设置文本 */
findViewById<TextView>(R.id.main_text_version).text = "当前版本:$moduleVersion" findViewById<TextView>(R.id.main_text_version).text = "当前版本:$moduleVersion"
findViewById<TextView>(R.id.main_text_support).text = "支持 $moduleSupport" findViewById<TextView>(R.id.main_text_support_qq).apply {
/*初始化 View*/ text = qqSupportVersion
setOnClickListener {
AlertDialog.Builder(this@MainActivity)
.setTitle("兼容的 QQ 版本")
.setMessage(qqSupportVersion)
.setPositiveButton("我知道了", null)
.show()
}
}
findViewById<TextView>(R.id.main_text_support_tim).apply {
text = timSupportVersion
setOnClickListener {
AlertDialog.Builder(this@MainActivity)
.setTitle("兼容的 TIM 版本")
.setMessage(timSupportVersion)
.setPositiveButton("我知道了", null)
.show()
}
}
findViewById<TextView>(R.id.main_text_support_wechat).apply {
text = wechatSupportVersion
setOnClickListener {
AlertDialog.Builder(this@MainActivity)
.setTitle("兼容的微信版本")
.setMessage(wechatSupportVersion)
.setPositiveButton("我知道了", null)
.show()
}
}
/** 初始化 View */
val protectModeSwitch = findViewById<SwitchCompat>(R.id.protect_mode_switch) val protectModeSwitch = findViewById<SwitchCompat>(R.id.protect_mode_switch)
val hideIconInLauncherSwitch = findViewById<SwitchCompat>(R.id.hide_icon_in_launcher_switch) val hideIconInLauncherSwitch = findViewById<SwitchCompat>(R.id.hide_icon_in_launcher_switch)
val notifyModuleInfoSwitch = findViewById<SwitchCompat>(R.id.notify_module_info_switch) val notifyModuleInfoSwitch = findViewById<SwitchCompat>(R.id.notify_module_info_switch)
/*获取 Sp 存储的信息*/ /** 获取 Sp 存储的信息 */
protectModeSwitch.isChecked = getBoolean("_white_mode") protectModeSwitch.isChecked = getBoolean("_white_mode")
hideIconInLauncherSwitch.isChecked = getBoolean("_hide_icon") hideIconInLauncherSwitch.isChecked = getBoolean("_hide_icon")
notifyModuleInfoSwitch.isChecked = getBoolean("_tip_run_info") notifyModuleInfoSwitch.isChecked = getBoolean("_tip_run_info")
protectModeSwitch.setOnCheckedChangeListener { btn, b -> protectModeSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener if (!btn.isPressed) return@setOnCheckedChangeListener
putBoolean("_white_mode", b) putBoolean(HookMedium.ENABLE_WHITE_MODE, b)
} }
hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b -> hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener if (!btn.isPressed) return@setOnCheckedChangeListener
putBoolean("_hide_icon", b) putBoolean(HookMedium.ENABLE_HIDE_ICON, b)
packageManager.setComponentEnabledSetting( packageManager.setComponentEnabledSetting(
ComponentName(this@MainActivity, "com.fankes.tsbattery.Home"), ComponentName(this@MainActivity, "com.fankes.tsbattery.Home"),
if (b) PackageManager.COMPONENT_ENABLED_STATE_DISABLED else PackageManager.COMPONENT_ENABLED_STATE_ENABLED, if (b) PackageManager.COMPONENT_ENABLED_STATE_DISABLED else PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
@@ -114,16 +150,15 @@ class MainActivity : AppCompatActivity() {
} }
notifyModuleInfoSwitch.setOnCheckedChangeListener { btn, b -> notifyModuleInfoSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener if (!btn.isPressed) return@setOnCheckedChangeListener
putBoolean("_tip_run_info", b) putBoolean(HookMedium.ENABLE_RUN_INFO, b)
} }
/*项目地址点击事件*/ /** 项目地址点击事件 */
findViewById<View>(R.id.link_with_project_address).setOnClickListener { findViewById<View>(R.id.link_with_project_address).setOnClickListener {
try { try {
val intent = Intent() startActivity(Intent().apply {
intent.action = "android.intent.action.VIEW" action = "android.intent.action.VIEW"
val content_url = Uri.parse("https://github.com/fankes/TSBattery") data = Uri.parse("https://github.com/fankes/TSBattery")
intent.data = content_url })
startActivity(intent)
} catch (e: Exception) { } catch (e: Exception) {
Toast.makeText(this, "无法启动系统默认浏览器", Toast.LENGTH_SHORT).show() Toast.makeText(this, "无法启动系统默认浏览器", Toast.LENGTH_SHORT).show()
} }
@@ -132,42 +167,9 @@ class MainActivity : AppCompatActivity() {
/** /**
* 判断模块是否激活 * 判断模块是否激活
* [HookMain] Hook 掉此方法
* @return 激活状态 * @return 激活状态
*/ */
private fun isHooked(): Boolean { private fun isHooked() = HookMedium.isHooked()
Log.d("TSBattery", "isHooked: true")
return isExpModuleActive()
}
/**
* 新增太极判断方式
* @return 是否激活
*/
private fun isExpModuleActive(): Boolean {
var isExp = false
try {
val uri = Uri.parse("content://me.weishu.exposed.CP/")
var result: Bundle? = null
try {
result = contentResolver.call(uri, "active", null, null)
} catch (e: RuntimeException) {
// TaiChi is killed, try invoke
try {
val intent = Intent("me.weishu.exp.ACTION_ACTIVE")
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
} catch (e1: Throwable) {
return false
}
}
if (result == null) result = contentResolver.call(uri, "active", null, null)
if (result == null) return false
isExp = result.getBoolean("active", false)
} catch (ignored: Throwable) {
}
return isExp
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
@@ -206,6 +208,24 @@ class MainActivity : AppCompatActivity() {
Context.MODE_PRIVATE Context.MODE_PRIVATE
).edit().putBoolean(key, bool).apply() ).edit().putBoolean(key, bool).apply()
setWorldReadable() setWorldReadable()
/** 延迟继续设置强制允许 SP 可读可写 */
Handler().postDelayed({ setWorldReadable() }, 500)
Handler().postDelayed({ setWorldReadable() }, 1000)
Handler().postDelayed({ setWorldReadable() }, 1500)
}
/**
* 保存值
* @param key 名称
* @param value
*/
private fun putString(key: String, value: String) {
getSharedPreferences(
packageName + "_preferences",
Context.MODE_PRIVATE
).edit().putString(key, value).apply()
setWorldReadable()
/** 延迟继续设置强制允许 SP 可读可写 */
Handler().postDelayed({ setWorldReadable() }, 500) Handler().postDelayed({ setWorldReadable() }, 500)
Handler().postDelayed({ setWorldReadable() }, 1000) Handler().postDelayed({ setWorldReadable() }, 1000)
Handler().postDelayed({ setWorldReadable() }, 1500) Handler().postDelayed({ setWorldReadable() }, 1500)
@@ -231,4 +251,15 @@ class MainActivity : AppCompatActivity() {
Toast.makeText(this, "无法写入模块设置,请检查权限\n如果此提示一直显示,请不要双开模块", Toast.LENGTH_SHORT).show() Toast.makeText(this, "无法写入模块设置,请检查权限\n如果此提示一直显示,请不要双开模块", Toast.LENGTH_SHORT).show()
} }
} }
override fun onBackPressed() {
setWorldReadable()
super.onBackPressed()
}
override fun onDestroy() {
super.onDestroy()
/** 销毁实例防止内存泄漏 */
instance = null
}
} }

View File

@@ -26,6 +26,8 @@ object XPrefUtils {
fun getBoolean(key: String) = pref.getBoolean(key, false) fun getBoolean(key: String) = pref.getBoolean(key, false)
fun getString(key: String) = pref.getString(key, "unknown")
private val pref: XSharedPreferences private val pref: XSharedPreferences
get() { get() {
val preferences = XSharedPreferences("com.fankes.tsbattery") val preferences = XSharedPreferences("com.fankes.tsbattery")

View File

@@ -4,8 +4,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
tools:context=".MainActivity" tools:context=".ui.MainActivity"
tools:ignore="HardcodedText"> tools:ignore="HardcodedText,UseCompoundDrawables,ContentDescription">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -28,7 +28,10 @@
android:id="@+id/main_lin_status" android:id="@+id/main_lin_status"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="15dp" android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:layout_marginRight="15dp"
android:layout_marginBottom="5dp"
android:background="@drawable/dark_round" android:background="@drawable/dark_round"
android:elevation="3dp" android:elevation="3dp"
android:gravity="center"> android:gravity="center">
@@ -61,27 +64,120 @@
android:id="@+id/main_text_version" android:id="@+id/main_text_version"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="3dp" android:layout_marginBottom="10dp"
android:alpha="0.8" android:alpha="0.8"
android:text="当前版本:%1" android:text="当前版本:%1"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="13sp" /> android:textSize="13sp" />
<TextView <LinearLayout
android:id="@+id/main_text_support" android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="3dp" android:layout_marginBottom="7dp"
android:alpha="0.8" android:gravity="center|start"
android:text="支持 %1" android:orientation="horizontal">
android:textColor="@color/white"
android:textSize="12sp" /> <ImageView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="5dp"
android:src="@mipmap/qq_icon" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:alpha="0.8"
android:text="兼容"
android:textColor="@color/white"
android:textSize="11sp" />
<TextView
android:id="@+id/main_text_support_qq"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.8"
android:ellipsize="end"
android:singleLine="true"
android:text="%1"
android:textColor="@color/white"
android:textSize="11sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="7dp"
android:gravity="center|start"
android:orientation="horizontal">
<ImageView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="5dp"
android:src="@mipmap/tim_icon" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:alpha="0.8"
android:text="兼容"
android:textColor="@color/white"
android:textSize="11sp" />
<TextView
android:id="@+id/main_text_support_tim"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.8"
android:ellipsize="end"
android:singleLine="true"
android:text="%1"
android:textColor="@color/white"
android:textSize="11sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:gravity="center|start"
android:orientation="horizontal">
<ImageView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="5dp"
android:src="@mipmap/wechat_icon" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:alpha="0.8"
android:text="兼容"
android:textColor="@color/white"
android:textSize="11sp" />
<TextView
android:id="@+id/main_text_support_wechat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.8"
android:ellipsize="end"
android:singleLine="true"
android:text="%1"
android:textColor="@color/white"
android:textSize="11sp" />
</LinearLayout>
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:alpha="0.6" android:alpha="0.6"
android:text="理论在小更新内还会生效,如果失效请看下方的联系方式" android:lineSpacingExtra="5dp"
android:text="上述列出的版本号为最佳兼容版本,你可以点击进行查看。\n这些版本在适配范围内的应用都将有效但可能不能达到最佳使用效果。\n如果当前版本失效请看下方的联系方式。"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="10sp" android:textSize="10sp"
tools:ignore="SmallSp" /> tools:ignore="SmallSp" />
@@ -129,7 +225,7 @@
android:layout_marginBottom="10dp" android:layout_marginBottom="10dp"
android:alpha="0.6" android:alpha="0.6"
android:lineSpacingExtra="6dp" android:lineSpacingExtra="6dp"
android:text="此选项默认关闭,默认情况下模块将会干掉 QQ 和 TIM 自身的电源锁控制类,开启后模块将只对系统电源锁生效,如果你的 QQ 或 TIM 视频通话等设置发生了故障,可以尝试开启这个功能,开启后请重启 QQ 或 TIM。" android:text="此选项默认关闭,默认情况下模块将会干掉 QQ 和 TIM 自身的电源锁控制类,开启后模块将只对系统电源锁生效,如果你的 QQ 或 TIM 视频通话等设置发生了故障,可以尝试开启这个功能,开启后请重启 QQ 或 TIM,暂不支持微信。"
android:textColor="#777777" android:textColor="#777777"
android:textSize="12sp" /> android:textSize="12sp" />
</LinearLayout> </LinearLayout>
@@ -161,7 +257,7 @@
android:layout_marginBottom="10dp" android:layout_marginBottom="10dp"
android:alpha="0.6" android:alpha="0.6"
android:lineSpacingExtra="6dp" android:lineSpacingExtra="6dp"
android:text="模块工作正常情况下不要开启,如果你想测试模块是否正常激活,可以打开此提示,开启后将会在启动 QQTIM 的时候提示运行信息。" android:text="模块工作正常情况下不要开启,如果你想测试模块是否正常激活,可以打开此提示,开启后将会在启动 QQTIM 或微信的时候提示运行信息。"
android:textColor="#777777" android:textColor="#777777"
android:textSize="12sp" /> android:textSize="12sp" />
</LinearLayout> </LinearLayout>
@@ -276,6 +372,16 @@
android:textColor="#777777" android:textColor="#777777"
android:textSize="12sp" /> android:textSize="12sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:text="Q.关于目前微信的适配情况?\nA.微信适配尚在实验阶段,敬请期待。"
android:textColor="#777777"
android:textSize="12sp" />
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -366,8 +472,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="10dp" android:layout_marginBottom="10dp"
android:src="@mipmap/qr_pay" android:src="@mipmap/qr_pay" />
tools:ignore="ContentDescription" />
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -1,15 +1,15 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = "1.4.20" ext.kotlin_version = "1.6.10"
repositories { repositories {
google() google()
//noinspection JcenterRepositoryObsolete maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
jcenter() maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' }
maven { url "https://www.jitpack.io" }
mavenCentral()
} }
dependencies { dependencies {
//noinspection AndroidGradlePluginVersion classpath "com.android.tools.build:gradle:7.0.4"
classpath "com.android.tools.build:gradle:4.1.1"
//noinspection DifferentKotlinGradleVersion
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
@@ -20,8 +20,10 @@ buildscript {
allprojects { allprojects {
repositories { repositories {
google() google()
//noinspection JcenterRepositoryObsolete maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
jcenter() maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' }
maven { url "https://www.jitpack.io" }
mavenCentral()
} }
} }

View File

@@ -1,6 +1,6 @@
#Sat Sep 04 04:05:23 CST 2021 #Sat Sep 04 04:05:23 CST 2021
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME