180 Commits
3.3 ... 4.2

Author SHA1 Message Date
3c30c532a4 Update version to 4.2 | Support many QQ 8.9.x version 2023-01-14 00:37:02 +08:00
8f7772d047 Modify remove localTime function time second format and change description text in GithubReleaseTool 2023-01-14 00:16:45 +08:00
c148a535c8 Modify change promote message in YukiPromoteTool 2023-01-14 00:13:53 +08:00
a5448a2c61 Modify remove "endsWith" method's param name statement 2023-01-14 00:12:55 +08:00
a8a01e8afa Modify change related description text in activity_main 2023-01-14 00:08:34 +08:00
c322c3dc29 Modify change related description text in MainActivity 2023-01-14 00:00:33 +08:00
2e9761f01a Added executor info and change some description text in ConfigActivity, activity_config 2023-01-13 23:57:52 +08:00
0fd1a33533 Modify remove "replace" method's param name statement 2023-01-13 23:20:26 +08:00
e465739f95 Modify remove instanceClass param for hookQQSettingsUI function in QQTIMHooker 2023-01-13 22:38:14 +08:00
52f992a6cd Modify change related description text in activity_config 2023-01-13 22:24:25 +08:00
37be07f11d Modify change isOnFailureThrowToApp to false for onAppLifecycle event in QQTIMHooker 2023-01-13 22:19:53 +08:00
d8d9fc8c41 Modify make HookEntry singleton 2023-01-13 22:13:50 +08:00
f27fdb2f25 Modify merge to YukiHookAPI new usage 2023-01-13 04:56:15 +08:00
3629ca3df5 Update Gradle & Kotlin
- Update Kotlin version to 1.7.22
- Update Gradle version to 7.6
- Update Gradle dependencies
2023-01-13 04:53:45 +08:00
12a1c12f8a Update YukiHookAPI 2023-01-13 04:50:50 +08:00
bc41ac2bdc Fix internal browser X5 kernel issues (cause QQ crashed) 2023-01-12 15:00:52 +08:00
5f13e203f0 Fix WeChat SettingsUI's top right TSBattery icon lost problem when version >= 8.0.28 2023-01-12 03:17:01 +08:00
69a68999a5 Fix not found QQSettingSettingFragment Class when not in Pad Mode (disabled error output) 2023-01-11 02:45:02 +08:00
bc48d59b59 Fix TaiChi activation state lost problem for API version higher than 30 2023-01-02 17:43:24 +08:00
cadf7b8873 Modify support QQ 8.9.28 2023-01-01 01:06:11 +08:00
7f0c8dd2b5 Modify support QQ 8.9.25 2022-12-16 02:21:14 +08:00
67349c45fd Modify support QQ 8.9.23 2022-11-30 22:37:34 +08:00
4ab612d5fa Modify add release channel description, release status description in README 2022-11-26 00:30:31 +08:00
bb4b942dae Added automatic build workflows for Github Actions 2022-11-26 00:29:54 +08:00
ff0931b7bc Modify support QQ 8.9.20 2022-11-18 01:50:26 +08:00
fc42a0ddf0 Modify support QQ 8.9.19 2022-11-08 23:45:27 +08:00
abab096ae0 Modify support QQ 8.9.18 2022-11-03 10:35:47 +08:00
ce8e786b5b Modify change logo url to raw in README 2022-11-03 10:35:32 +08:00
dd6c69b337 Modify remove wrap/part at the end of file 2022-11-03 10:18:47 +08:00
69564cccf6 Update Gradle & PlatformSDK
- Update Kotlin version to 1.7.20
2022-11-03 10:17:50 +08:00
a8c16d7823 Modify support QQ Pad Mode for QQ Settings UI entry item since 8.9.15 2022-10-22 02:00:57 +08:00
3616919978 Update Gradle dependencies 2022-10-22 01:11:11 +08:00
c5dfbcbe0d Modify support QQ 8.9.15 2022-10-22 01:09:32 +08:00
f21e8769e0 Modify change not support version log to warn level in QQTIMHooker 2022-10-21 21:24:57 +08:00
eada17db44 Update Gradle & PlatformSDK
- Update Android Gradle Plugin version to 7.3.1
- Update Kotlin version to 1.7.20
2022-10-20 00:20:13 +08:00
67209859c4 Modify merge to new usage in HookEntry 2022-10-04 07:29:37 +08:00
a65b3c1dbe Update YukiHookAPI 2022-10-04 07:28:56 +08:00
6375aa7844 Update README.md 2022-10-02 22:57:46 +08:00
cbd6fd038d Modify merge all png elements to svg elements 2022-10-02 22:55:44 +08:00
7fcf9228f4 Modify change icon to svg in activity_main 2022-10-02 21:38:49 +08:00
d995c69b5c Fix variable name shadowed bug in QQTIMHooker 2022-10-02 21:26:17 +08:00
376a2a5890 Modify compatible with API 33 2022-10-01 02:16:55 +08:00
e2ff60e2ef Modify standard code naming in FunctionFactory 2022-10-01 00:46:56 +08:00
ad0b6d253a Update YukiHookAPI 2022-09-30 23:47:24 +08:00
76af063ed8 Modify change code note in MainActivity 2022-09-30 22:21:16 +08:00
e0fdf269a0 Modify optimize code style in HookEntry 2022-09-30 22:14:07 +08:00
b1d4cd4017 Added Project icon 2022-09-30 22:10:48 +08:00
1337d7cf31 Update version to 4.1 | Support QQ 8.9.13 2022-09-30 05:18:28 +08:00
8855595b80 Fix Non-Activity dialog destroy problem in DialogBuilderFactory 2022-09-30 05:16:31 +08:00
4bf53efa7a Fix QQ, TIM make Activity DPI deformation problem 2022-09-30 05:06:12 +08:00
7f2e97a94e Modify change Activity Proxy Class from QQSettingSettingActivity to AboutActivity in QQTIMHooker 2022-09-30 04:57:38 +08:00
16e0278369 Modify support QQ、TIM night mode theme 2022-09-30 04:57:17 +08:00
20f6d5529f Update YuiHookAPI 2022-09-30 03:39:29 +08:00
08d7676478 Fix change hook entry inject function try fix some Non-Root Hook Framework not work, such as TaiChi 2022-09-30 00:02:48 +08:00
a67faed81c Modify support QQ 8.9.13 2022-09-29 21:38:31 +08:00
5f67b54f36 Update version to 4.0 | Support QQ 8.9.3, 8.9.5, 8.9.8 and 8.9.10 2022-09-29 08:50:09 +08:00
d607b5900e Added new module configs info in activity_main 2022-09-29 08:44:56 +08:00
2831506340 Added directly open module app function in ConfigActivity 2022-09-29 08:37:35 +08:00
bdcfdda874 Update proguard-rules.pro 2022-09-29 08:21:34 +08:00
2cdc8258cd Modify move main configs function to Host App and refactored 2022-09-29 08:06:08 +08:00
8b0e37e5b1 Modify change net work state judgment in GithubReleaseTool 2022-09-29 07:16:57 +08:00
9a8b99f1a8 Modify add some function in FunctionFactory 2022-09-29 07:16:50 +08:00
dc8b137f18 Modify add special Resources Id in completing 2022-09-28 20:44:26 +08:00
085120811d Modify make BaseActivity extends ModuleAppCompatActivity 2022-09-28 20:42:51 +08:00
2e4bc3e802 Modify change hideOrShowLauncherIcon in a hard-code way 2022-09-28 20:29:04 +08:00
06ef371f82 Modify merge to new DialogBuilderFactory 2022-09-28 20:12:09 +08:00
f7f4427c61 Modify merge YukiHookAPI new usage and remove DataConst.ENABLE_MODULE_VERSION 2022-09-28 20:10:51 +08:00
5542cdef64 Update Gradle & PlatformSDK 2022-09-28 19:53:27 +08:00
4c55ff93f1 Update YuiHookAPI 2022-09-28 19:51:40 +08:00
fef1bf9225 Support QQ 8.9.10 2022-09-17 11:12:14 +08:00
5f59ee226d Support QQ 8.9.8 2022-09-01 23:46:16 +08:00
e187e637f9 Support QQ 8.9.3 and 8.9.5 2022-08-17 21:03:36 +08:00
c01cc2532e Update Gradle dependencies 2022-08-17 20:58:14 +08:00
8b657a8aab Update version to 3.99.6 | Support QQ 8.9.2 2022-07-21 19:22:03 +08:00
f0ab310620 Support QQ 8.9.2 2022-07-21 19:20:43 +08:00
711e0ac730 Update version to 3.99.5 | Support many QQ 8.x.x version 2022-07-20 22:58:46 +08:00
b1e83a1073 Update gradle version 2022-07-20 22:54:43 +08:00
b562337995 Update Kotlin version to 1.7.10 2022-07-20 22:50:16 +08:00
6b990c50f8 ReFormat code style 2022-07-20 22:48:26 +08:00
Fankesyooni
bae91688b1 Merge pull request #20 from chase535/patch-1
适配大量 QQ 8.x.x
2022-07-20 12:50:43 +08:00
chase535
a59d260321 Update MainActivity.kt 2022-07-20 12:28:24 +08:00
chase535
e850a73c49 Update HookEntry.kt 2022-07-20 11:38:09 +08:00
chase535
1dc725c294 适配大量 QQ 8.x.x
适配了绝大多数(可能是全部)8.x.x版本的正式版QQ
2022-07-20 11:13:58 +08:00
ce89da3ee8 Merge dependencies 2022-07-20 02:08:42 +08:00
81331dfe81 Update version to 3.99 | Support QQ 8.9.0 and 8.9.1 2022-07-19 22:11:34 +08:00
20f1ceca4d Support QQ 8.9.0 and 8.9.1 2022-07-19 22:02:04 +08:00
fc8589f1ee Fix hook BaseChatPie failure bug 2022-07-19 21:57:06 +08:00
Fankesyooni
67378b2ebf Merge pull request #19 from StarWishsama/patch-1
适配 QQ 8.9.0
2022-07-17 01:04:11 +08:00
NoRainCity
bf4ecd8345 Update QQ 8.9.0 2022-07-16 20:53:33 +08:00
6e8d900dc5 Update version to 3.98 | Support QQ 8.8.98 2022-07-05 22:49:18 +08:00
3d12d3f4a5 Added QQ version support situation 2022-07-05 22:45:17 +08:00
4b36437dac Modify BugHook luckily 2022-07-05 22:04:04 +08:00
93789f163b Support QQ 8.8.98 2022-07-05 22:01:49 +08:00
32d8937ddb Update version to 3.97 | Support QQ 8.8.95 2022-06-16 02:25:01 +08:00
38c9d78f1e Update Gradle & Kotlin & PlatformSDK
- Update Kotlin version to 1.7.0
- Update Gradle dependencies
- Merge legacy code
2022-06-10 17:13:58 +08:00
0ee03340da Merge hook function 2022-06-10 17:10:51 +08:00
54ee2358cf Make UI to Primary Theme 2022-06-08 15:07:23 +08:00
55708eb96a Merge DialogBuilderFactory with new code style 2022-06-07 16:55:29 +08:00
c4de8ae448 Update version to 3.96 | Support QQ 8.8.93 2022-06-04 03:20:38 +08:00
9e092742da Fix GithubReleaseTool to LocalTime 2022-06-04 03:04:54 +08:00
fb27c107f4 Added BuildConfig.VERSION_NAME changed 2022-06-03 23:54:21 +08:00
2be9a3d934 Update version to 3.95 2022-05-31 02:06:35 +08:00
d7dbecb65c Update YukiHookAPI 2022-05-31 02:02:26 +08:00
6c2b3f12b3 Merge code 2022-05-30 02:11:30 +08:00
f1c520586a Merge code 2022-05-30 02:03:14 +08:00
e9c99343c3 Merge code 2022-05-30 01:43:47 +08:00
20de713ffd Merge systemBar support with native 2022-05-30 00:47:47 +08:00
3b78e8a515 Repair Licenses 2022-05-30 00:16:53 +08:00
cf898092bd Update version to 3.9 | Support QQ 8.8.90 2022-05-30 00:10:35 +08:00
251a5151df Added YukiPromoteTool 2022-05-30 00:03:57 +08:00
59e93bc040 Support QQ 8.8.90 2022-05-29 22:51:25 +08:00
d440e85be4 Update YukiHookAPI 2022-05-29 04:12:54 +08:00
e1dc99de01 Update YukiHookAPI 2022-05-27 03:34:16 +08:00
4eab1f7046 Update YukiHookAPI 2022-05-25 04:36:25 +08:00
3fc294dd8d Update YukiHookAPI 2022-05-10 01:52:07 +08:00
a2c06cc201 Update YukiHookAPI 2022-05-06 15:01:27 +08:00
6f39d5fdab Update YukiHookAPI 2022-05-04 14:01:41 +08:00
5ada7585e3 Update YukiHookAPI 2022-05-04 10:14:29 +08:00
31b8f157e5 Update YukiHookAPI 2022-05-04 09:31:13 +08:00
e71f137f74 Update Kotlin version 2022-05-04 07:01:04 +08:00
9e9bbcc8ca Update YukiHookAPI 2022-05-04 07:00:17 +08:00
8043457a6e Update version to 3.8,support QQ 8.8.88 2022-05-01 12:26:13 +08:00
d4a1536d70 Update YukiHookAPI 2022-05-01 12:15:20 +08:00
5c784f65f8 Merge code 2022-04-25 02:46:13 +08:00
1c6c6e6480 Update YukiHookAPI 2022-04-18 03:12:32 +08:00
4712d0063b Update YukiHookAPI 2022-04-18 03:11:27 +08:00
583c72470b Merge code 2022-04-15 15:26:01 +08:00
8cf945b670 Update YukiHookAPI 2022-04-15 05:11:06 +08:00
6fba93e1fa Merge code 2022-04-13 04:48:52 +08:00
288563ef68 Merge code 2022-04-13 04:46:15 +08:00
d1e4c17817 Update YukiHookAPI 2022-04-13 04:42:25 +08:00
c8ec50cb29 Update YukiHookAPI 2022-04-10 03:13:58 +08:00
b6e042b9d0 Update YukiHookAPI 2022-04-09 02:28:05 +08:00
3c4789d9a0 Update README.md 2022-04-09 01:48:48 +08:00
46ff5f4bcf Update YukiHookAPI 2022-04-05 22:46:12 +08:00
09eeeeab5f Update YukiHookAPI 2022-04-04 22:53:27 +08:00
f285e6f96d Update version to 3.7 2022-04-04 03:06:17 +08:00
fb90d4051b Update YukiHookAPI 2022-04-04 02:59:32 +08:00
911eb042a6 修复新版 QQ 设置页面的圆角问题 2022-04-01 13:25:38 +08:00
9d48732140 修复新版 QQ 设置页面的圆角问题 2022-04-01 13:25:18 +08:00
2d31d46cd2 Update version to 3.6,support QQ 8.8.85 2022-03-31 15:04:17 +08:00
3a1a20e688 Merge code 2022-03-31 15:00:34 +08:00
24346c056f 加入设置页面显示守护状态功能,加入新的省电 Hook 点 2022-03-31 14:58:54 +08:00
e05c8fce30 适配 QQ 8.8.85 2022-03-31 12:38:17 +08:00
e1b15c4c0a Remove some hook when bug 2022-03-31 12:28:15 +08:00
5b99288ea8 Update YukiHookAPI 2022-03-30 14:12:01 +08:00
da958920b3 Merge code 2022-03-29 21:06:31 +08:00
035e07db89 Update YukiHookAPI 2022-03-29 21:06:11 +08:00
90de1612f3 Update YukiHookAPI 2022-03-29 21:04:49 +08:00
9d2a87449d Merge code 2022-03-28 14:19:13 +08:00
7a8bc6c8f8 Merge code 2022-03-28 13:58:34 +08:00
b1e1e15412 Update YukiHookAPI 2022-03-28 00:38:44 +08:00
569d4e278a 添加本地化 LSPosed 作用域 2022-03-25 14:22:58 +08:00
2729724e93 Update YukiHookAPI 2022-03-25 01:54:34 +08:00
64f87bd69f Update YukiHookAPI 2022-03-25 01:07:26 +08:00
8d80d0b11f Update version to 3.5 2022-03-24 15:37:14 +08:00
c045529914 Update version to 3.5 2022-03-24 15:36:12 +08:00
6799e1e52d Merge code 2022-03-24 15:31:20 +08:00
b11bd6dc46 Merge code 2022-03-24 15:23:22 +08:00
99a203bde6 Merge code 2022-03-24 15:22:47 +08:00
99b736ef36 Merge code 2022-03-24 15:21:41 +08:00
415ab22bfc Merge code 2022-03-23 00:07:09 +08:00
5bfa7c74d3 Merge code 2022-03-23 00:06:48 +08:00
2ba2ff0b35 Merge code 2022-03-22 02:58:32 +08:00
3ac6647a25 Merge code 2022-03-22 01:32:11 +08:00
18b6b907bf 更新模块 UI 以及相关设置 2022-03-21 14:40:17 +08:00
73762291f1 更新模块 UI 以及相关设置 2022-03-21 14:17:29 +08:00
49563d8bc5 Update README.md 2022-03-20 22:28:36 +08:00
0678357048 增加模块自动检查更新功能 2022-03-20 22:28:21 +08:00
b49b4b9b9e 增加模块自动检查更新功能 2022-03-20 22:28:06 +08:00
af927d3f19 增加模块自动检查更新功能 2022-03-20 14:06:11 +08:00
73be5df304 增加模块自动检查更新功能 2022-03-20 13:55:36 +08:00
e01102a928 增加模块自动检查更新功能 2022-03-20 13:19:05 +08:00
a21d27b65c Merge code 2022-03-20 12:11:36 +08:00
45dc67c609 Update YuKiHookAPI 2022-03-20 03:37:16 +08:00
c3c2c07b00 Merge code 2022-03-20 00:54:08 +08:00
1b573ec296 Merge R8 Rules 2022-03-19 20:33:30 +08:00
95601466ef 布局更换为 ViewBinding 并适配 MD3 风格对话框 2022-03-19 14:31:48 +08:00
07c52eb10a Merge code 2022-03-19 01:40:28 +08:00
81aa8a7ef9 Update YuKiHookAPI 2022-03-18 23:51:10 +08:00
69 changed files with 3014 additions and 2983 deletions

63
.github/workflows/commit_ci.yml vendored Normal file
View File

@@ -0,0 +1,63 @@
name: Automatic Build on Commit
on:
workflow_dispatch:
push:
branches: [ master ]
paths-ignore:
- '**.md'
- '**.txt'
- '.github/**'
- '!.github/workflows/**'
jobs:
build:
name: Build CI
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup cmake
uses: jwlawson/actions-setup-cmake@v1
with:
cmake-version: '3.22.1'
- name: Prepare Java 11
uses: actions/setup-java@v3
with:
java-version: 11
java-package: jdk
distribution: 'temurin'
cache: 'gradle'
- name: Cache Gradle Dependencies
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
!~/.gradle/caches/build-cache-*
key: gradle-deps-core-${{ hashFiles('**/build.gradle') }}
restore-keys: |
gradle-deps
- name: Cache Gradle Build
uses: actions/cache@v3
with:
path: |
~/.gradle/caches/build-cache-*
key: gradle-builds-core-${{ github.sha }}
restore-keys: |
gradle-builds
- name: Build with Gradle
run: |
./gradlew :app:assembleDebug
./gradlew :app:assembleRelease
echo "DEBUG_APK_FILE=$(find app/build/outputs/apk/debug -name '*.apk')" >> $GITHUB_ENV
echo "RELEASE_APK_FILE=$(find app/build/outputs/apk/release -name '*.apk')" >> $GITHUB_ENV
- name: Upload Artifacts(debug)
uses: actions/upload-artifact@v3
with:
path: ${{ env.DEBUG_APK_FILE }}
name: app-debug
- name: Upload Artifacts(release)
uses: actions/upload-artifact@v3
with:
path: ${{ env.RELEASE_APK_FILE }}
name: app-release

62
.github/workflows/pull_request_ci.yml vendored Normal file
View File

@@ -0,0 +1,62 @@
name: Pull Request Checker
on:
pull_request:
branches: [ master ]
paths-ignore:
- '**.md'
- '**.txt'
- '.github/**'
- '!.github/workflows/**'
jobs:
build:
name: Pull request check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup cmake
uses: jwlawson/actions-setup-cmake@v1
with:
cmake-version: '3.22.1'
- name: Prepare Java 11
uses: actions/setup-java@v3
with:
java-version: 11
java-package: jdk
distribution: 'temurin'
cache: 'gradle'
- name: Cache Gradle Dependencies
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
!~/.gradle/caches/build-cache-*
key: gradle-deps-core-${{ hashFiles('**/build.gradle') }}
restore-keys: |
gradle-deps
- name: Cache Gradle Build
uses: actions/cache@v3
with:
path: |
~/.gradle/caches/build-cache-*
key: gradle-builds-core-${{ github.sha }}
restore-keys: |
gradle-builds
- name: Build with Gradle
run: |
./gradlew :app:assembleDebug
./gradlew :app:assembleRelease
echo "DEBUG_APK_FILE=$(find app/build/outputs/apk/debug -name '*.apk')" >> $GITHUB_ENV
echo "RELEASE_APK_FILE=$(find app/build/outputs/apk/release -name '*.apk')" >> $GITHUB_ENV
- name: Upload Artifacts(debug)
uses: actions/upload-artifact@v3
with:
path: ${{ env.DEBUG_APK_FILE }}
name: app-debug
- name: Upload Artifacts(release)
uses: actions/upload-artifact@v3
with:
path: ${{ env.RELEASE_APK_FILE }}
name: app-release

BIN
.idea/icon.png generated Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

6
.idea/kotlinc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.7.20" />
</component>
</project>

3
.idea/misc.xml generated
View File

@@ -4,12 +4,13 @@
<option name="filePathToZoomLevelMap">
<map>
<entry key="app/src/main/res/drawable-v24/dark_round.xml" value="0.4482051282051282" />
<entry key="app/src/main/res/drawable/bg_orange_round.xml" value="0.229" />
<entry key="app/src/main/res/drawable/bg_permotion_round.xml" value="0.2495" />
<entry key="app/src/main/res/drawable/button_round.xml" value="0.44871794871794873" />
<entry key="app/src/main/res/drawable/dark_round.xml" value="0.4482051282051282" />
<entry key="app/src/main/res/drawable/permotion_round.xml" value="0.4482051282051282" />
<entry key="app/src/main/res/drawable/red_round.xml" value="0.4482051282051282" />
<entry key="app/src/main/res/layout/activity_main.xml" value="0.3504380475594493" />
<entry key="app/src/main/res/layout/activity_main.xml" value="0.3413246647704185" />
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" value="0.2495" />
</map>
</option>

View File

@@ -35,7 +35,7 @@ fun a(test: String) {
val a = "" // 变量注释
```
- ⚠️注意:只允许两个 // 后方要有空格
- ⚠️ 注意:只允许两个 // 后方要有空格
## 项目要求

View File

@@ -1,40 +1,74 @@
# TSBattery
![Eclipse Marketplace](https://img.shields.io/badge/build-passing-brightgreen)
![Eclipse Marketplace](https://img.shields.io/badge/license-AGPL3.0-blue)
![Eclipse Marketplace](https://img.shields.io/badge/version-v3.3-green)
[![Telegram](https://img.shields.io/static/v1?label=Telegram&message=交流讨论&color=0088cc)](https://t.me/XiaofangInternet)
[![Blank](https://img.shields.io/badge/build-passing-brightgreen)](https://github.com/fankes/TSBattery)
[![Blank](https://img.shields.io/badge/license-AGPL3.0-blue)](https://github.com/fankes/TSBattery/blob/master/LICENSE)
[![Blank](https://img.shields.io/badge/version-v4.2-green)](https://github.com/fankes/TSBattery/releases)
[![Blank](https://img.shields.io/github/downloads/fankes/TSBattery/total?label=Release)](https://github.com/fankes/TSBattery/releases)
[![Blank](https://img.shields.io/github/downloads/Xposed-Modules-Repo/com.fankes.tsbattery/total?label=LSPosed%20Repo&logo=Android&style=flat&labelColor=F48FB1&logoColor=ffffff)](https://github.com/Xposed-Modules-Repo/com.fankes.tsbattery/releases)
[![Telegram](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram)](https://t.me/XiaofangInternet)
<br/><br/>
![banner](https://github.com/fankes/TSBattery/blob/master/banner.png)<br/>
A new way to save your battery avoid cancer apps hacker it.<br/>
![banner](https://github.com/fankes/TSBattery/blob/master/banner.png?raw=true)<br/>
A new way to save your battery avoid cancer apps hacker it.
TSBattery 是一个旨在使 QQ、TIM、微信 变得更省电的开源 Xposed 模块。
# 开始使用
## Developer
点击下载最新版本
<a href='https://github.com/fankes/TSBattery/releases'>![Eclipse Marketplace](https://img.shields.io/badge/download-v3.3-green)</a>
<br/><br/>
⚠️ 适配说明<br/>
[酷安 @星夜不荟](http://www.coolapk.com/u/876977)
- 支持并建议使用 <b>LSPosed</b>(若作用域没有自动出现推荐请勾选 QQ、TIM、微信)
- 可以使用 <b>~~EdXposed~~</b>,但随时停止支持
- <b>太极无极 · 阴</b> 支持性不是很好,建议使用 <b>太极无极 · 阳</b>
- 支持 <b>Pine</b>(梦境模块) 但是部分功能有限制
- 请不要使用 <b>~~应用转生~~</b>,发生封号情况后果自负
## 适配说明
# 请勿用于非法用途
- 支持并建议使用 **LSPosed**(若作用域没有自动出现推荐请勾选 QQ、TIM、微信)
- 本模块完全开源免费,如果好用你可以打赏支持开发,但是请不要用于非法用途。
- 本模块发布地址仅有 [Xposed-Modules-Repo](https://github.com/Xposed-Modules-Repo/com.fankes.tsbattery/releases)、
[Release](https://github.com/fankes/TSBattery/releases) 及[蓝奏云](https://fankes.lanzouy.com/b02zfz3sj),从其他非正规渠道下载到的版本或对您造成任何影响均与我们无关。
- 可以使用 **~~EdXposed~~**,但随时停止支持
# 开始贡献
- **太极无极 · 阴** 支持性不是很好,建议使用 **太极无极 · 阳****LSPatch (推荐)**
- 支持 **Pine**(梦境模块) 但是部分功能有限制
- 请不要使用 **~~应用转生~~**,发生封号情况后果自负
- 如果微信不能在 **LSPosed** 中生效,请尝试勾选任意包含微信作用域的模块,例如 **微X模块**
## 请勿用于非法用途
本模块完全开源免费,如果好用你可以打赏支持开发,但是请不要用于非法用途。
## 发行渠道说明
- [Automatic Build on Commit](https://github.com/fankes/TSBattery/actions/workflows/commit_ci.yml)
上述更新为代码 `commit` 后自动触发,具体更新内容可点击上方的文字前往 **Github Actions** 进行查看,本更新由开源的流程自动编译发布,**不保证其稳定性**,所发布的版本**仅供测试**,且不会特殊说明甚至可能会变更版本号或保持与当前稳定版相同的版本号。
- [Release](https://github.com/fankes/TSBattery/releases)
- [Xposed-Modules-Repo](https://github.com/Xposed-Modules-Repo/com.fankes.tsbattery/releases)
- [蓝奏云 **密码tsbt**](https://fankes.lanzouy.com/b02zfz3sj)
上述更新为手动发布的稳定版,具体更新内容可点击上方的文字前往指定的发布页面查看,稳定版的更新将会同时发布到上述地址中,同步更新。
本模块发布地址仅限于上述所列出的地址,从其他非正规渠道下载到的版本或对您造成任何影响均与我们无关。
## 发行状态说明
![Blank](https://img.shields.io/badge/build-passing-brightgreen)
上述状态为当前稳定版与自动构建版本一致或当前代码改动与稳定版无功能差异。
![Blank](https://img.shields.io/badge/build-pending-dbab09)
上述状态为存在自动构建版本和新功能的更新但当前并未发布稳定版,处于预发行状态。
![Blank](https://img.shields.io/badge/build-problem-red)
上述状态为当前发行的稳定版可能存在严重问题但并未及时进行修复且并未发布稳定版。
## 开始贡献
欢迎为此项目进行新版本的适配代码贡献!<br/>
- [CONTRIBUTING](https://github.com/fankes/TSBattery/blob/master/CONTRIBUTING.md)
# 许可证
## 许可证
- [AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.html)
@@ -55,5 +89,6 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
```
Powered by [YukiHookAPI](https://github.com/fankes/YukiHookAPI)<br/><br/>
版权所有 © 2019-2022 Fankes Studio(qzmmcn@163.com)
Powered by [YukiHookAPI](https://github.com/fankes/YukiHookAPI)
版权所有 © 2019-2022 Fankes Studio(qzmmcn@163.com)

View File

@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.devtools.ksp' version '1.6.10-1.0.2'
id 'com.google.devtools.ksp' version '1.7.22-1.0.8'
}
android {
@@ -15,13 +15,12 @@ android {
v2SigningEnabled true
}
}
compileSdkVersion 31
buildToolsVersion "31.0.0"
compileSdkVersion 33
defaultConfig {
applicationId "com.fankes.tsbattery"
minSdk 22
targetSdk 26
targetSdk 33
versionCode rootProject.ext.appVersionCode
versionName rootProject.ext.appVersionName
@@ -30,46 +29,45 @@ android {
buildTypes {
release {
minifyEnabled true
minifyEnabled rootProject.ext.enableR8
shrinkResources rootProject.ext.enableR8
zipAlignEnabled rootProject.ext.enableR8
signingConfig signingConfigs.debug
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = '1.8'
jvmTarget = '11'
freeCompilerArgs = [
'-Xno-param-assertions',
'-Xno-call-assertions',
'-Xno-receiver-assertions'
]
}
buildFeatures {
viewBinding true
}
lintOptions {
checkReleaseBuilds false
}
aaptOptions.additionalParameters '--allow-reserved-package-id', '--package-id', '0x64'
}
dependencies {
compileOnly 'de.robv.android.xposed:api:82'
implementation 'com.highcapable.yukihookapi:api:1.0.5.2'
ksp 'com.highcapable.yukihookapi:ksp-xposed:1.0.5.2'
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'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'com.highcapable.yukihookapi:api:1.1.5'
ksp 'com.highcapable.yukihookapi:ksp-xposed:1.1.5'
implementation 'com.github.duanhong169:drawabletoolbox:1.0.7'
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.7'
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
/** 移除无效耗时 lint Task */
tasks.whenTaskAdded {
task -> if (task.name == "lintVitalRelease") task.enabled = false
}
/** 移除无效耗时 lint Task */
tasks.whenTaskAdded {
task -> if (task.name == "lintVitalAnalyzeRelease") task.enabled = false
}
/** 移除无效耗时 lint Task */
tasks.whenTaskAdded {
task -> if (task.name == "lintVitalReportRelease") task.enabled = false
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}

View File

@@ -20,29 +20,28 @@
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-dontwarn
-ignorewarnings
-optimizationpasses 10
-dontusemixedcaseclassnames
-dontoptimize
-verbose
-overloadaggressively
-repackageclasses o
-allowaccessmodification
-adaptclassstrings
-adaptresourcefilenames
-adaptresourcefilecontents
#-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
-renamesourcefileattribute H
-renamesourcefileattribute P
-keepattributes SourceFile,LineNumberTable
-keep class android.support**
-keep class androidx**
# 排除注入的 Activity
-keep class com.fankes.tsbattery.ui.activity.parasitic.ConfigActivity
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
public static *** throwUninitializedProperty(...);
public static *** throwUninitializedPropertyAccessException(...);
}
-keepclassmembers class * implements androidx.viewbinding.ViewBinding {
*** inflate(android.view.LayoutInflater);
}

View File

@@ -3,11 +3,18 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.fankes.tsbattery">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- 作用域 APP -->
<queries>
<package android:name="com.tencent.mobileqq" />
<package android:name="com.tencent.tim" />
<package android:name="com.tencent.mm" />
<intent>
<action android:name="android.intent.action.MAIN" />
</intent>
</queries>
<application
@@ -23,17 +30,18 @@
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="Tencent 社交毒瘤一键省电模块。\n目前支持 QQ、TIM、微信\n开发者酷安 @星夜不荟" />
<meta-data
android:name="xposedminversion"
android:value="93" />
<meta-data
android:name="xposedscope"
android:resource="@array/module_scope" />
<activity
android:name="com.fankes.tsbattery.ui.MainActivity"
android:name="com.fankes.tsbattery.ui.activity.MainActivity"
android:exported="true"
android:screenOrientation="behind">
@@ -49,7 +57,7 @@
android:exported="true"
android:label="@string/app_name"
android:screenOrientation="behind"
android:targetActivity="com.fankes.tsbattery.ui.MainActivity">
android:targetActivity="com.fankes.tsbattery.ui.activity.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@@ -19,31 +19,15 @@
*
* 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
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication
class TSApplication : Application() {
companion object {
/** 全局静态实例 */
private var context: TSApplication? = null
/**
* 调用全局静态实例
* @return [TSApplication]
*/
val appContext get() = context ?: error("App is death")
}
class TSApplication : ModuleApplication() {
override fun onCreate() {
super.onCreate()
/** 设置静态实例 */
context = this
/** 跟随系统夜间模式 */
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}

View File

@@ -17,18 +17,30 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
* This file is Created by fankes on 2022/9/29.
*/
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
package com.fankes.tsbattery.const
import android.graphics.drawable.Drawable
/**
* 包名常量定义类
*/
object PackageName {
abstract class DrawableWrapperBuilder<T: DrawableWrapperBuilder<T>> {
/** QQ */
const val QQ = "com.tencent.mobileqq"
protected var drawable: Drawable? = null
/** TIM */
const val TIM = "com.tencent.tim"
@Suppress("UNCHECKED_CAST")
fun drawable(drawable: Drawable): T = apply { this.drawable = drawable } as T
/** 微信 */
const val WECHAT = "com.tencent.mm"
}
abstract fun build(): Drawable
/**
* 跳转常量定义类
*/
object JumpEvent {
/** 启动模块设置 */
const val OPEN_MODULE_SETTING = "tsbattery_open_module_settings"
}

View File

@@ -0,0 +1,125 @@
/*
* TSBattery - A new way to save your battery avoid cancer apps hacker it.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/TSBattery
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/9/28.
*/
package com.fankes.tsbattery.data
import android.content.Context
import android.content.SharedPreferences
import android.widget.CompoundButton
/**
* 全局配置存储控制类
*/
object ConfigData {
/** QQ、TIM 保守模式*/
const val ENABLE_QQ_TIM_PROTECT_MODE = "enable_qq_tim_protect_mode"
/** 自动关闭 QQ、TIM 的 CoreService */
const val ENABLE_KILL_QQ_TIM_CORESERVICE = "enable_kill_qq_tim_core_service"
/** 自动关闭 QQ、TIM 的 CoreService$KernelService */
const val ENABLE_KILLE_QQ_TIM_CORESERVICE_CHILD = "enable_kill_qq_tim_core_service_child"
/** 停用全部省电功能 (停用模块) */
const val DISABLE_ALL_HOOK = "disable_all_hook"
/** 当前的 [SharedPreferences] */
private var sharePrefs: SharedPreferences? = null
/**
* 读取 [SharedPreferences]
* @param key 键值名称
* @param value 键值内容
* @return [Boolean]
*/
private fun getBoolean(key: String, value: Boolean = false) = sharePrefs?.getBoolean(key, value) ?: value
/**
* 存入 [SharedPreferences]
* @param key 键值名称
* @param value 键值内容
*/
private fun putBoolean(key: String, value: Boolean = false) = sharePrefs?.edit()?.putBoolean(key, value)?.apply()
/**
* 初始化 [SharedPreferences]
* @param context 实例
*/
fun init(context: Context) {
sharePrefs = context.getSharedPreferences("tsbattery_config", Context.MODE_PRIVATE)
}
/**
* 绑定到 [CompoundButton] 自动设置选中状态
* @param key 键值名称
* @param onChange 当改变时回调
*/
fun CompoundButton.bind(key: String, onChange: (Boolean) -> Unit = {}) {
isChecked = getBoolean(key)
setOnCheckedChangeListener { button, isChecked ->
if (button.isPressed) {
putBoolean(key, isChecked)
onChange(isChecked)
}
}
}
/**
* 是否启用 QQ、TIM 保守模式
* @return [Boolean]
*/
var isEnableQQTimProtectMode
get() = getBoolean(ENABLE_QQ_TIM_PROTECT_MODE)
set(value) {
putBoolean(ENABLE_QQ_TIM_PROTECT_MODE, value)
}
/**
* 是否启用自动关闭 QQ、TIM 的 CoreService
* @return [Boolean]
*/
var isEnableKillQQTimCoreService
get() = getBoolean(ENABLE_KILL_QQ_TIM_CORESERVICE)
set(value) {
putBoolean(ENABLE_KILL_QQ_TIM_CORESERVICE, value)
}
/**
* 是否启用自动关闭 QQ、TIM 的 CoreService$KernelService
* @return [Boolean]
*/
var isEnableKillQQTimCoreServiceChild
get() = getBoolean(ENABLE_KILLE_QQ_TIM_CORESERVICE_CHILD)
set(value) {
putBoolean(ENABLE_KILLE_QQ_TIM_CORESERVICE_CHILD, value)
}
/**
* 是否停用全部省电功能 (停用模块)
* @return [Boolean]
*/
var isDisableAllHook
get() = getBoolean(DISABLE_ALL_HOOK)
set(value) {
putBoolean(DISABLE_ALL_HOOK, value)
}
}

View File

@@ -1,38 +0,0 @@
/*
* TSBattery - A new way to save your battery avoid cancer apps hacker it.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/TSBattery
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2021/11/9.
*/
package com.fankes.tsbattery.hook
object HookConst {
const val ENABLE_HIDE_ICON = "_hide_icon"
const val ENABLE_RUN_INFO = "_tip_run_info"
const val ENABLE_NOTIFY_TIP = "_tip_in_notify"
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 QQ_PACKAGE_NAME = "com.tencent.mobileqq"
const val TIM_PACKAGE_NAME = "com.tencent.tim"
const val WECHAT_PACKAGE_NAME = "com.tencent.mm"
}

View File

@@ -23,494 +23,28 @@
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_NOTIFY_TIP
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.factory.showDialog
import com.fankes.tsbattery.utils.factory.versionCode
import com.fankes.tsbattery.utils.factory.versionName
import com.fankes.tsbattery.const.PackageName
import com.fankes.tsbattery.hook.entity.QQTIMHooker
import com.fankes.tsbattery.hook.entity.WeChatHooker
import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed
import com.highcapable.yukihookapi.hook.bean.VariousClass
import com.highcapable.yukihookapi.hook.factory.configs
import com.highcapable.yukihookapi.hook.factory.encase
import com.highcapable.yukihookapi.hook.factory.field
import com.highcapable.yukihookapi.hook.log.loggerD
import com.highcapable.yukihookapi.hook.log.loggerE
import com.highcapable.yukihookapi.hook.param.PackageParam
import com.highcapable.yukihookapi.hook.type.android.*
import com.highcapable.yukihookapi.hook.type.java.*
import com.highcapable.yukihookapi.hook.xposed.proxy.YukiHookXposedInitProxy
import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit
@InjectYukiHookWithXposed
class HookEntry : YukiHookXposedInitProxy {
@InjectYukiHookWithXposed(isUsingResourcesHook = false)
object HookEntry : IYukiHookXposedInit {
companion object {
/** QQ、TIM 存在的类 */
private const val SplashActivityClass = "$QQ_PACKAGE_NAME.activity.SplashActivity"
/** QQ、TIM 存在的类 */
private const val CoreServiceClass = "$QQ_PACKAGE_NAME.app.CoreService"
/** QQ、TIM 存在的类 */
private const val CoreService_KernelServiceClass = "$QQ_PACKAGE_NAME.app.CoreService\$KernelService"
/** 微信存在的类 */
private const val LauncherUIClass = "$WECHAT_PACKAGE_NAME.ui.LauncherUI"
/** 根据多个版本存的不同的类 */
private val BaseChatPieClass = VariousClass(
"$QQ_PACKAGE_NAME.activity.aio.core.BaseChatPie",
"$QQ_PACKAGE_NAME.activity.BaseChatPie"
)
}
/**
* 这个类 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", "8.8.80" -> {
interceptBaseChatPie(methodName = "bk")
interceptBaseChatPie(methodName = "bl")
}
"8.8.83" -> {
interceptBaseChatPie(methodName = "bl")
interceptBaseChatPie(methodName = "bm")
}
else -> loggerD(msg = "$version not supported!")
}
}
/**
* 拦截 [BaseChatPieClass] 的目标方法体封装
* @param methodName 方法名
*/
private fun PackageParam.interceptBaseChatPie(methodName: String) =
BaseChatPieClass.hook {
injectMember {
method {
name = methodName
returnType = UnitType
}
intercept()
}
}
/** Hook 系统电源锁 */
private fun PackageParam.hookSystemWakeLock() =
PowerManager_WakeLockClass.hook {
injectMember {
method {
name = "acquireLocked"
returnType = UnitType
}
intercept()
}
}
/** 增加通知栏文本显示守护状态 */
private fun PackageParam.hookNotification() =
Notification_BuilderClass.hook {
injectMember {
method {
name = "setContentText"
param(CharSequenceType)
}
beforeHook {
if (prefs.getBoolean(ENABLE_NOTIFY_TIP, default = true))
when (firstArgs 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 ->
SplashActivityClass.hook {
/**
* Hook 启动界面的第一个 [Activity]
* QQ 和 TIM 都是一样的类
* 在里面加入提示运行信息的对话框测试模块是否激活
*/
injectMember {
method {
name = "doOnCreate"
param(BundleClass)
}
afterHook {
instance<Activity>().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 ->
LauncherUIClass.hook {
/**
* Hook 启动界面的第一个 [Activity]
* 在里面加入提示运行信息的对话框测试模块是否激活
*/
injectMember {
method {
name = "onCreate"
param(BundleClass)
}
afterHook {
instance<Activity>().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))
CoreServiceClass.hook {
if (isQQ) {
injectMember {
method { name = "startTempService" }
intercept()
}
injectMember {
method {
name = "startCoreService"
param(BooleanType)
}
intercept()
}
injectMember {
method {
name = "onStartCommand"
param(IntentClass, IntType, IntType)
}
replaceTo(any = 2)
}
}
injectMember {
method { name = "onCreate" }
afterHook {
instance<Service>().apply {
stopForeground(true)
stopService(Intent(applicationContext, javaClass))
loggerD(msg = "Shutdown CoreService OK!")
}
}
}
}
if (prefs.getBoolean(ENABLE_QQTIM_CORESERVICE_CHILD_BAN))
CoreService_KernelServiceClass.hook {
injectMember {
method { name = "onCreate" }
afterHook {
instance<Service>().apply {
stopForeground(true)
stopService(Intent(applicationContext, javaClass))
loggerD(msg = "Shutdown CoreService\$KernelService OK!")
}
}
}
injectMember {
method {
name = "onStartCommand"
param(IntentClass, IntType, IntType)
}
replaceTo(any = 2)
}
}
}
/** 是否完全支持当前版本 */
var isHookClientSupport = true
override fun onInit() = configs {
debugTag = "TSBattery"
debugLog { tag = "TSBattery" }
isDebug = false
isEnableModulePrefsCache = false
isEnableDataChannel = false
}
override fun onHook() = encase {
loadApp(QQ_PACKAGE_NAME) {
hookSystemWakeLock()
hookNotification()
hookCoreService(isQQ = true)
hookModuleRunningInfo(isQQTIM = true)
if (prefs.getBoolean(ENABLE_QQTIM_WHITE_MODE)) return@loadApp
/** 通过在 SplashActivity 里取到应用的版本号 */
SplashActivityClass.hook {
injectMember {
method {
name = "doOnCreate"
param(BundleClass)
}
afterHook { hookQQBaseChatPie(instance<Activity>().versionName) }
}
}
/**
* 一个不知道是什么作用的电源锁
* 同样直接干掉
*/
findClass(name = "com.tencent.mars.ilink.comm.WakerLock").hook {
injectMember {
method {
name = "lock"
param(LongType)
}
intercept()
}.ignoredAllFailure()
}
/**
* 一个不知道是什么作用的电源锁
* 同样直接干掉
*/
findClass(name = "com.tencent.mars.comm.WakerLock").hook {
injectMember {
method {
name = "lock"
param(LongType)
}
intercept()
}.ignoredAllFailure()
injectMember {
method {
name = "lock"
param(StringType)
}
intercept()
}.ignoredAllFailure()
injectMember {
method { name = "lock" }
intercept()
}.ignoredAllFailure()
}
/**
* 干掉消息收发功能的电源锁
* 每个版本的差异暂未做排查
* 旧版本理论上没有这个类
*/
findClass(name = "$QQ_PACKAGE_NAME.msf.service.y").hook {
injectMember {
method {
name = "a"
param(StringType, LongType)
returnType = UnitType
}
intercept()
}.onAllFailure { loggerE(msg = "Hook MsfService Failed $it") }
}
/**
* 干掉自动上传服务的电源锁
* 每个版本的差异暂未做排查
*/
findClass(name = "com.tencent.upload.impl.UploadServiceImpl").hook {
injectMember {
method { name = "acquireWakeLockIfNot" }
intercept()
}.onAllFailure { loggerE(msg = "Hook UploadServiceImpl Failed $it") }
}
/**
* Hook 掉一个一像素保活 [Activity] 真的我怎么都想不到讯哥的程序员做出这种事情
* 这个东西经过测试会在锁屏的时候吊起来,解锁的时候自动 finish(),无限耍流氓耗电
* 2022/1/25 后期查证:锁屏界面消息快速回复窗口的解锁后拉起保活界面,也是毒瘤
*/
findClass(name = "$QQ_PACKAGE_NAME.activity.QQLSUnlockActivity").hook {
injectMember {
method {
name = "onCreate"
param(BundleClass)
}
var origDevice = ""
beforeHook {
/** 由于在 onCreate 里有一行判断只要型号是 xiaomi 的设备就开电源锁,所以说这里临时替换成菊花厂 */
origDevice = Build.MANUFACTURER
if (Build.MANUFACTURER.lowercase() == "xiaomi")
BuildClass.field { name = "MANUFACTURER" }.get().set("HUAWEI")
}
afterHook {
instance<Activity>().finish()
/** 这里再把型号替换回去 - 不影响应用变量等 Xposed 模块修改的型号 */
BuildClass.field { name = "MANUFACTURER" }.get().set(origDevice)
}
}
}
/**
* 这个东西同上
* 反正也是一个一像素保活的 [Activity]
* 讯哥的程序员真的有你的
* 2022/1/25 后期查证:锁屏界面消息快速回复窗口
*/
findClass(name = "$QQ_PACKAGE_NAME.activity.QQLSActivity\$14").hook {
injectMember {
method { name = "run" }
intercept()
}.ignoredAllFailure()
}.ignoredHookClassNotFoundFailure()
/**
* 这个是毒瘤核心类
* WakeLockMonitor
* 这个名字真的起的特别诗情画意
* 带给用户的却是 shit 一样的体验
* 里面有各种使用 Handler 和 Timer 的各种耗时常驻后台耗电办法持续接收消息
* 直接循环全部方法全部干掉
* 👮🏻 经过排查 Play 版本没这个类...... Emmmm 不想说啥了
*/
findClass(name = "com.tencent.qapmsdk.qqbattery.monitor.WakeLockMonitor").hook {
injectMember {
method {
name = "onHook"
param(StringType, AnyType, AnyArrayClass, AnyType)
}
intercept()
}
injectMember {
method {
name = "doReport"
param(("com.tencent.qapmsdk.qqbattery.monitor.WakeLockMonitor\$WakeLockEntity").clazz, IntType)
}
intercept()
}
injectMember {
method {
name = "afterHookedMethod"
param(("com.tencent.qapmsdk.qqbattery.monitor.MethodHookParam").clazz)
}
intercept()
}
injectMember {
method {
name = "beforeHookedMethod"
param(("com.tencent.qapmsdk.qqbattery.monitor.MethodHookParam").clazz)
}
intercept()
}
injectMember {
method { name = "onAppBackground" }
intercept()
}
injectMember {
method {
name = "onOtherProcReport"
param(BundleClass)
}
intercept()
}
injectMember {
method { name = "onProcessRun30Min" }
intercept()
}
injectMember {
method { name = "onProcessBG5Min" }
intercept()
}
injectMember {
method {
name = "writeReport"
param(BooleanType)
}
intercept()
}
}.ignoredHookClassNotFoundFailure()
}
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 = "ウイチャット:それが機能するかどうかはわかりませんでした")
}
loadApp(PackageName.QQ, PackageName.TIM) { loadHooker(QQTIMHooker) }
loadApp(PackageName.WECHAT, WeChatHooker)
}
}

View File

@@ -0,0 +1,625 @@
/*
* TSBattery - A new way to save your battery avoid cancer apps hacker it.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/TSBattery
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/9/29.
*/
package com.fankes.tsbattery.hook.entity
import android.app.Activity
import android.app.Service
import android.content.res.Configuration
import android.os.Build
import android.view.View
import android.view.ViewGroup
import androidx.core.app.ServiceCompat
import androidx.fragment.app.Fragment
import com.fankes.tsbattery.BuildConfig
import com.fankes.tsbattery.const.PackageName
import com.fankes.tsbattery.data.ConfigData
import com.fankes.tsbattery.hook.HookEntry
import com.fankes.tsbattery.hook.factory.hookSystemWakeLock
import com.fankes.tsbattery.hook.factory.jumpToModuleSettings
import com.fankes.tsbattery.hook.factory.startModuleSettings
import com.fankes.tsbattery.utils.factory.appVersionName
import com.fankes.tsbattery.utils.factory.dp
import com.highcapable.yukihookapi.hook.bean.VariousClass
import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
import com.highcapable.yukihookapi.hook.factory.*
import com.highcapable.yukihookapi.hook.log.loggerD
import com.highcapable.yukihookapi.hook.log.loggerE
import com.highcapable.yukihookapi.hook.log.loggerI
import com.highcapable.yukihookapi.hook.log.loggerW
import com.highcapable.yukihookapi.hook.type.android.*
import com.highcapable.yukihookapi.hook.type.java.*
/**
* Hook QQ、TIM
*/
object QQTIMHooker : YukiBaseHooker() {
/** QQ、TIM 存在的类 */
const val JumpActivityClass = "${PackageName.QQ}.activity.JumpActivity"
/** QQ、TIM 存在的类 */
private const val QQSettingSettingActivityClass = "${PackageName.QQ}.activity.QQSettingSettingActivity"
/** QQ 新版存在的类 (Pad 模式) */
private const val QQSettingSettingFragmentClass = "${PackageName.QQ}.fragment.QQSettingSettingFragment"
/** QQ、TIM 存在的类 */
private const val AboutActivityClass = "${PackageName.QQ}.activity.AboutActivity"
/** QQ、TIM 新版本存在的类 */
private const val FormSimpleItemClass = "${PackageName.QQ}.widget.FormSimpleItem"
/** QQ、TIM 旧版本存在的类 */
private const val FormCommonSingleLineItemClass = "${PackageName.QQ}.widget.FormCommonSingleLineItem"
/** QQ、TIM 存在的类 */
private const val CoreServiceClass = "${PackageName.QQ}.app.CoreService"
/** QQ、TIM 存在的类 */
private const val CoreService_KernelServiceClass = "${PackageName.QQ}.app.CoreService\$KernelService"
/** 根据多个版本存的不同的类 */
private val BaseChatPieClass =
VariousClass("${PackageName.QQ}.activity.aio.core.BaseChatPie", "${PackageName.QQ}.activity.BaseChatPie")
/** 一个内部进程的名称 (与 X5 浏览器内核有关) */
private val privilegedProcessName = "$packageName:privileged_process"
/** 默认的 [Configuration] */
var baseConfiguration: Configuration? = null
/**
* 当前是否为 QQ
* @return [Boolean]
*/
private val isQQ get() = packageName == PackageName.QQ
/** 当前宿主的版本 */
private var hostVersionName = "<unknown>"
/**
* 通过 [Activity] or [Fragment] 实例得到上下文
* @return [Activity] or null
*/
private fun Any.compatToActivity() = if (this is Activity) this else current().method { name = "getActivity"; superClass() }.invoke()
/**
* 这个类 QQ 的 BaseChatPie 是控制聊天界面的
*
* 里面有两个随机混淆的方法 ⬇
*
* remainScreenOn、cancelRemainScreenOn
*
* 这两个方法一个是挂起电源锁常驻亮屏
*
* 一个是停止常驻亮屏
*
* 不由分说每个版本混淆的方法名都会变
*
* 所以说每个版本重新适配 - 也可以提交分支帮我适配
*
* - ❗Hook 错了方法会造成闪退!
*/
private fun hookQQBaseChatPie() {
if (isQQ) when (hostVersionName) {
"8.0.0" -> {
hookBaseChatPie(methodName = "bq")
hookBaseChatPie(methodName = "aL")
}
"8.0.5", "8.0.7" -> {
hookBaseChatPie(methodName = "bw")
hookBaseChatPie(methodName = "aQ")
}
"8.1.0", "8.1.3" -> {
hookBaseChatPie(methodName = "bE")
hookBaseChatPie(methodName = "aT")
}
"8.1.5" -> {
hookBaseChatPie(methodName = "bF")
hookBaseChatPie(methodName = "aT")
}
"8.1.8", "8.2.0", "8.2.6" -> {
hookBaseChatPie(methodName = "bC")
hookBaseChatPie(methodName = "aT")
}
"8.2.7", "8.2.8", "8.2.11", "8.3.0" -> {
hookBaseChatPie(methodName = "bE")
hookBaseChatPie(methodName = "aV")
}
"8.3.5" -> {
hookBaseChatPie(methodName = "bR")
hookBaseChatPie(methodName = "aX")
}
"8.3.6" -> {
hookBaseChatPie(methodName = "cp")
hookBaseChatPie(methodName = "aX")
}
"8.3.9" -> {
hookBaseChatPie(methodName = "cj")
hookBaseChatPie(methodName = "aT")
}
"8.4.1", "8.4.5" -> {
hookBaseChatPie(methodName = "ck")
hookBaseChatPie(methodName = "aT")
}
"8.4.8", "8.4.10", "8.4.17", "8.4.18", "8.5.0" -> {
hookBaseChatPie(methodName = "remainScreenOn")
hookBaseChatPie(methodName = "cancelRemainScreenOn")
}
"8.5.5" -> {
hookBaseChatPie(methodName = "bT")
hookBaseChatPie(methodName = "aN")
}
"8.6.0", "8.6.5", "8.7.0", "8.7.5", "8.7.8", "8.8.0", "8.8.3", "8.8.5" -> {
hookBaseChatPie(methodName = "ag")
hookBaseChatPie(methodName = "ah")
}
"8.8.11", "8.8.12" -> {
hookBaseChatPie(methodName = "bc")
hookBaseChatPie(methodName = "bd")
}
"8.8.17", "8.8.20" -> {
hookBaseChatPie(methodName = "bd")
hookBaseChatPie(methodName = "be")
}
"8.8.23", "8.8.28" -> {
hookBaseChatPie(methodName = "bf")
hookBaseChatPie(methodName = "bg")
}
"8.8.33" -> {
hookBaseChatPie(methodName = "bg")
hookBaseChatPie(methodName = "bh")
}
"8.8.35", "8.8.38" -> {
hookBaseChatPie(methodName = "bi")
hookBaseChatPie(methodName = "bj")
}
"8.8.50" -> {
hookBaseChatPie(methodName = "bj")
hookBaseChatPie(methodName = "bk")
}
"8.8.55", "8.8.68", "8.8.80" -> {
hookBaseChatPie(methodName = "bk")
hookBaseChatPie(methodName = "bl")
}
"8.8.83", "8.8.85", "8.8.88", "8.8.90" -> {
hookBaseChatPie(methodName = "bl")
hookBaseChatPie(methodName = "bm")
}
"8.8.93", "8.8.95" -> {
hookBaseChatPie(methodName = "J3")
hookBaseChatPie(methodName = "S")
}
"8.8.98" -> {
hookBaseChatPie(methodName = "M3")
hookBaseChatPie(methodName = "S")
}
"8.9.0", "8.9.1", "8.9.2" -> {
hookBaseChatPie(methodName = "N3")
hookBaseChatPie(methodName = "S")
}
"8.9.3", "8.9.5" -> {
hookBaseChatPie(methodName = "H3")
hookBaseChatPie(methodName = "P")
}
"8.9.8", "8.9.10" -> {
hookBaseChatPie(methodName = "H3")
hookBaseChatPie(methodName = "N")
}
"8.9.13" -> {
hookBaseChatPie(methodName = "y3")
hookBaseChatPie(methodName = "H")
}
"8.9.15", "8.9.18", "8.9.19", "8.9.20" -> {
hookBaseChatPie(methodName = "w3")
hookBaseChatPie(methodName = "H")
}
"8.9.23", "8.9.25" -> {
hookBaseChatPie(methodName = "z3")
hookBaseChatPie(methodName = "H")
}
"8.9.28" -> {
hookBaseChatPie(methodName = "A3")
hookBaseChatPie(methodName = "H")
}
else -> {
HookEntry.isHookClientSupport = false
loggerW(msg = "$hostVersionName not supported!")
}
}
}
/**
* 拦截 [BaseChatPieClass] 的目标方法体封装
* @param methodName 方法名
*/
private fun hookBaseChatPie(methodName: String) {
BaseChatPieClass.hook {
injectMember {
method {
name = methodName
emptyParam()
returnType = UnitType
}
intercept()
}
}
}
/** Hook CoreService QQ、TIM */
private fun hookCoreService() {
CoreServiceClass.hook {
if (isQQ) {
injectMember {
method { name = "startTempService" }
intercept()
}.ignoredNoSuchMemberFailure()
injectMember {
method {
name = "startCoreService"
param(BooleanType)
}
intercept()
}.ignoredNoSuchMemberFailure()
injectMember {
method {
name = "onStartCommand"
param(IntentClass, IntType, IntType)
}
replaceTo(any = 2)
}.ignoredNoSuchMemberFailure()
}
injectMember {
method { name = "onCreate" }
afterHook {
if (ConfigData.isEnableKillQQTimCoreService)
instance<Service>().apply {
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
stopSelf()
loggerD(msg = "Shutdown CoreService OK!")
}
}
}
}
CoreService_KernelServiceClass.hook {
injectMember {
method { name = "onCreate" }
afterHook {
if (ConfigData.isEnableKillQQTimCoreServiceChild)
instance<Service>().apply {
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
stopSelf()
loggerD(msg = "Shutdown CoreService\$KernelService OK!")
}
}
}
injectMember {
method {
name = "onStartCommand"
param(IntentClass, IntType, IntType)
}
replaceTo(any = 2)
}.ignoredNoSuchMemberFailure()
}
}
/** Hook QQ 不省电的功能 */
private fun hookQQDisgusting() {
if (isQQ.not()) return
/**
* 干掉消息收发功能的电源锁
* 每个版本的差异暂未做排查
* 旧版本理论上没有这个类
*/
findClass(name = "${PackageName.QQ}.msf.service.y").hook {
injectMember {
method {
name = "a"
param(StringClass, LongType)
returnType = UnitType
}
intercept()
}.onAllFailure { loggerE(msg = "Hook MsfService Failed $it") }
}.ignoredHookClassNotFoundFailure()
/**
* 干掉自动上传服务的电源锁
* 每个版本的差异暂未做排查
*/
findClass(name = "com.tencent.upload.impl.UploadServiceImpl").hook {
injectMember {
method { name = "acquireWakeLockIfNot" }
intercept()
}.onAllFailure { loggerE(msg = "Hook UploadServiceImpl Failed $it") }
}.ignoredHookClassNotFoundFailure()
/**
* Hook 掉一个一像素保活 [Activity] 真的我怎么都想不到讯哥的程序员做出这种事情
* 这个东西经过测试会在锁屏的时候吊起来,解锁的时候自动 finish(),无限耍流氓耗电
* 2022/1/25 后期查证:锁屏界面消息快速回复窗口的解锁后拉起保活界面,也是毒瘤
*/
findClass(name = "${PackageName.QQ}.activity.QQLSUnlockActivity").hook {
injectMember {
method {
name = "onCreate"
param(BundleClass)
}
var origDevice = ""
beforeHook {
/** 由于在 onCreate 里有一行判断只要型号是 xiaomi 的设备就开电源锁,所以说这里临时替换成菊花厂 */
origDevice = Build.MANUFACTURER
if (Build.MANUFACTURER.lowercase() == "xiaomi")
BuildClass.field { name = "MANUFACTURER" }.get().set("HUAWEI")
}
afterHook {
instance<Activity>().finish()
/** 这里再把型号替换回去 - 不影响应用变量等 Xposed 模块修改的型号 */
BuildClass.field { name = "MANUFACTURER" }.get().set(origDevice)
}
}
}
/**
* 这个东西同上
* 反正也是一个一像素保活的 [Activity]
* 讯哥的程序员真的有你的
* 2022/1/25 后期查证:锁屏界面消息快速回复窗口
*/
findClass("${PackageName.QQ}.activity.QQLSActivity\$14", "ktq").hook {
injectMember {
method { name = "run" }
intercept()
}.ignoredAllFailure()
}.ignoredHookClassNotFoundFailure()
/**
* 这个是毒瘤核心类
* WakeLockMonitor
* 这个名字真的起的特别诗情画意
* 带给用户的却是 shit 一样的体验
* 里面有各种使用 Handler 和 Timer 的各种耗时常驻后台耗电办法持续接收消息
* 直接循环全部方法全部干掉
* 👮🏻 经过排查 Play 版本没这个类...... Emmmm 不想说啥了
* ✅ 备注8.9.x 版本已经基本移除了这个功能,没有再发现存在这个类
*/
findClass(name = "com.tencent.qapmsdk.qqbattery.monitor.WakeLockMonitor").hook {
injectMember {
method {
name = "onHook"
param(StringClass, AnyClass, AnyArrayClass, AnyClass)
}
intercept()
}
injectMember {
method {
name = "doReport"
param("com.tencent.qapmsdk.qqbattery.monitor.WakeLockMonitor\$WakeLockEntity", IntType)
}
intercept()
}
injectMember {
method {
name = "afterHookedMethod"
param("com.tencent.qapmsdk.qqbattery.monitor.MethodHookParam")
}
intercept()
}
injectMember {
method {
name = "beforeHookedMethod"
param("com.tencent.qapmsdk.qqbattery.monitor.MethodHookParam")
}
intercept()
}
injectMember {
method { name = "onAppBackground" }
intercept()
}
injectMember {
method {
name = "onOtherProcReport"
param(BundleClass)
}
intercept()
}
injectMember {
method { name = "onProcessRun30Min" }
intercept()
}
injectMember {
method { name = "onProcessBG5Min" }
intercept()
}
injectMember {
method {
name = "writeReport"
param(BooleanType)
}
intercept()
}
}.ignoredHookClassNotFoundFailure()
/**
* 这个是毒瘤核心操作类
* 功能同上、全部拦截
* 👮🏻 经过排查 Play 版本也没这个类...... Emmmm 不想说啥了
* ✅ 备注8.9.x 版本已经基本移除了这个功能,没有再发现存在这个类
*/
findClass(name = "com.tencent.qapmsdk.qqbattery.QQBatteryMonitor").hook {
injectMember {
method { name = "start" }
intercept()
}
injectMember {
method { name = "stop" }
intercept()
}
injectMember {
method {
name = "handleMessage"
param(MessageClass)
}
replaceToTrue()
}
injectMember {
method { name = "startMonitorInner" }
intercept()
}
injectMember {
method { name = "onAppBackground" }
intercept()
}
injectMember {
method { name = "onAppForeground" }
intercept()
}
injectMember {
method {
name = "setLogWhite"
paramCount = 2
}
intercept()
}
injectMember {
method {
name = "setCmdWhite"
paramCount = 2
}
intercept()
}
injectMember {
method {
name = "onWriteLog"
param(StringClass, StringClass)
}
intercept()
}
injectMember {
method {
name = "onCmdRequest"
param(StringClass)
}
intercept()
}
injectMember {
method {
name = "addData"
paramCount = 4
}
intercept()
}
injectMember {
method {
name = "onGpsScan"
paramCount = 2
}
intercept()
}
}.ignoredHookClassNotFoundFailure()
}
/**
* Hook QQ 的设置界面添加模块设置入口
* @param instance 当前设置界面实例
*/
private fun hookQQSettingsUI(instance: Any?) {
/** 当前的顶级 Item 实例 */
val formItemRefRoot = instance?.current()?.field {
type { it.name == FormSimpleItemClass || it.name == FormCommonSingleLineItemClass }.index(num = 1)
}?.cast<View?>()
/** 创建一个新的 Item */
FormSimpleItemClass.toClassOrNull()?.buildOf<View>(instance?.compatToActivity()) { param(ContextClass) }?.current {
method {
name = "setLeftText"
param(CharSequenceClass)
}.call("TSBattery")
method {
name = "setRightText"
param(CharSequenceClass)
}.call("${BuildConfig.VERSION_NAME}(${BuildConfig.VERSION_CODE})")
method {
name = "setBgType"
param(IntType)
}.call(if (isQQ) 0 else 2)
}?.apply { setOnClickListener { context.startModuleSettings() } }?.also { item ->
var listGroup = formItemRefRoot?.parent as? ViewGroup?
val lparam = (if (listGroup?.childCount == 1) {
listGroup = listGroup.parent as? ViewGroup
(formItemRefRoot?.parent as? View?)?.layoutParams
} else formItemRefRoot?.layoutParams)
?: ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
/** 设置圆角和间距 */
if (isQQ) (lparam as? ViewGroup.MarginLayoutParams?)?.setMargins(0, 15.dp(item.context), 0, 0)
/** 将 Item 添加到设置界面 */
listGroup?.also { if (isQQ) it.addView(item, lparam) else it.addView(item, 0, lparam) }
}
}
override fun onHook() {
onAppLifecycle(isOnFailureThrowToApp = false) {
attachBaseContext { baseContext, hasCalledSuper ->
if (hasCalledSuper.not()) baseConfiguration = baseContext.resources.configuration
}
onCreate {
hostVersionName = appVersionName
/** 不注入此进程防止部分系统发生 X5 浏览器内核崩溃问题 */
if (processName.startsWith(privilegedProcessName)) return@onCreate
ConfigData.init(context = this)
registerModuleAppActivities(AboutActivityClass)
if (ConfigData.isDisableAllHook) return@onCreate
hookSystemWakeLock()
hookQQBaseChatPie()
hookCoreService()
hookQQDisgusting()
loggerI(msg = "All processes are completed for \"${processName.takeIf { it != packageName } ?: packageName}\"")
}
}
/** 仅注入主进程 */
withProcess(mainProcessName) {
/** Hook 跳转事件 */
JumpActivityClass.hook {
injectMember {
method {
name = "doOnCreate"
param(BundleClass)
}
afterHook { instance<Activity>().jumpToModuleSettings() }
}
}
/** 将条目注入设置界面 (Activity) */
QQSettingSettingActivityClass.hook {
injectMember {
method {
name = "doOnCreate"
param(BundleClass)
}
afterHook { hookQQSettingsUI(instance) }
}
}
/** 将条目注入设置界面 (Fragment) */
QQSettingSettingFragmentClass.hook {
injectMember {
method {
name = "doOnCreateView"
paramCount = 3
}
afterHook { hookQQSettingsUI(instance) }
}
}.ignoredHookClassNotFoundFailure()
}
}
}

View File

@@ -0,0 +1,151 @@
/*
* TSBattery - A new way to save your battery avoid cancer apps hacker it.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/TSBattery
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/9/29.
*/
package com.fankes.tsbattery.hook.entity
import android.app.Activity
import android.content.Context
import android.os.Build
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.core.content.res.ResourcesCompat
import com.fankes.tsbattery.R
import com.fankes.tsbattery.const.PackageName
import com.fankes.tsbattery.data.ConfigData
import com.fankes.tsbattery.hook.factory.hookSystemWakeLock
import com.fankes.tsbattery.hook.factory.jumpToModuleSettings
import com.fankes.tsbattery.hook.factory.startModuleSettings
import com.fankes.tsbattery.utils.factory.absoluteStatusBarHeight
import com.fankes.tsbattery.utils.factory.appVersionCode
import com.fankes.tsbattery.utils.factory.appVersionName
import com.fankes.tsbattery.utils.factory.dp
import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
import com.highcapable.yukihookapi.hook.factory.current
import com.highcapable.yukihookapi.hook.factory.injectModuleAppResources
import com.highcapable.yukihookapi.hook.factory.processName
import com.highcapable.yukihookapi.hook.factory.registerModuleAppActivities
import com.highcapable.yukihookapi.hook.log.loggerI
import com.highcapable.yukihookapi.hook.type.android.ViewClass
/**
* Hook 微信
*
* 具体功能还在画饼
*/
object WeChatHooker : YukiBaseHooker() {
/** 微信存在的类 - 未测试每个版本是否都存在 */
const val LauncherUIClass = "${PackageName.WECHAT}.ui.LauncherUI"
/** 微信存在的类 - 未测试每个版本是否都存在 */
private const val EmptyActivityClass = "${PackageName.WECHAT}.ui.EmptyActivity"
/** 微信存在的类 - 未测试每个版本是否都存在 */
private const val WelabMainUIClass = "${PackageName.WECHAT}.plugin.welab.ui.WelabMainUI"
/** 微信存在的类 - 未测试每个版本是否都存在 */
private const val SettingsUIClass = "${PackageName.WECHAT}.plugin.setting.ui.setting.SettingsUI"
/** TSBattery 图标 TAG 名称 */
private const val TSBARRERY_ICON_TAG = "tsbattery_icon"
/**
* 创建 TSBattery 图标
* @param context 当前实例
* @return [LinearLayout]
*/
private fun createPreferenceIcon(context: Context) = LinearLayout(context).apply {
tag = TSBARRERY_ICON_TAG
gravity = Gravity.END or Gravity.BOTTOM
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
addView(ImageView(context).apply {
layoutParams = ViewGroup.MarginLayoutParams(20.dp(context), 20.dp(context)).apply {
topMargin = context.absoluteStatusBarHeight + 15.dp(context)
rightMargin = 20.dp(context)
}
setColorFilter(ResourcesCompat.getColor(resources, R.color.colorTextGray, null))
setImageResource(R.drawable.ic_icon)
if (Build.VERSION.SDK_INT >= 26) tooltipText = "TSBattery 设置"
setOnClickListener { context.startModuleSettings() }
})
}
override fun onHook() {
onAppLifecycle {
onCreate {
ConfigData.init(context = this)
registerModuleAppActivities(
when {
EmptyActivityClass.hasClass() -> EmptyActivityClass
WelabMainUIClass.hasClass() -> WelabMainUIClass
else -> error("Inject WeChat Activity Proxy failed, unsupport version $appVersionName($appVersionCode)")
}
)
if (ConfigData.isDisableAllHook) return@onCreate
hookSystemWakeLock()
loggerI(msg = "All processes are completed for \"${processName.takeIf { it != packageName } ?: packageName}\"")
}
}
/** 仅注入主进程 */
withProcess(mainProcessName) {
/** Hook 跳转事件 */
LauncherUIClass.hook {
injectMember {
method {
name = "onResume"
emptyParam()
}
afterHook { instance<Activity>().jumpToModuleSettings(isFinish = false) }
}
}
/** 向设置界面右上角添加按钮 */
SettingsUIClass.hook {
injectMember {
method {
name = "onResume"
emptyParam()
}
afterHook {
method {
name = "get_fragment"
emptyParam()
superClass(isOnlySuperClass = true)
}.get(instance).call()?.current()?.method {
name = "getView"
emptyParam()
returnType = ViewClass
superClass(isOnlySuperClass = true)
}?.invoke<ViewGroup?>()?.also {
it.context?.injectModuleAppResources()
runCatching { it.getChildAt(0) as? ViewGroup? }.getOrNull()?.also { rootView ->
if (rootView.findViewWithTag<View>(TSBARRERY_ICON_TAG) == null)
rootView.addView(createPreferenceIcon(it.context))
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,93 @@
/*
* TSBattery - A new way to save your battery avoid cancer apps hacker it.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/TSBattery
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/9/29.
*/
package com.fankes.tsbattery.hook.factory
import android.app.Activity
import android.content.Context
import android.content.Intent
import androidx.appcompat.app.AppCompatDelegate
import com.fankes.tsbattery.const.JumpEvent
import com.fankes.tsbattery.const.PackageName
import com.fankes.tsbattery.hook.entity.QQTIMHooker.toClass
import com.fankes.tsbattery.ui.activity.parasitic.ConfigActivity
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.hook.bean.VariousClass
import com.highcapable.yukihookapi.hook.factory.field
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.param.PackageParam
import com.highcapable.yukihookapi.hook.type.android.PowerManager_WakeLockClass
import kotlin.system.exitProcess
/** QQ、TIM 存在的类 */
private const val MobileQQClass = "mqq.app.MobileQQ"
/** QQ、TIM 存在的类 */
private val ThemeUtilClass = VariousClass("${PackageName.QQ}.theme.ThemeUtil", "${PackageName.QQ}.vas.theme.api.ThemeUtil")
/**
* QQ、TIM 主题是否为夜间模式
* @return [Boolean]
*/
private fun Context.isQQNightMode() = runCatching {
ThemeUtilClass.get(classLoader).method {
name = "getUserCurrentThemeId"
paramCount = 1
}.get().string(MobileQQClass.toClass(classLoader)
.field { name = "sMobileQQ" }.ignored().get().current(ignored = true)?.field { name = "mAppRuntime" }?.any()
).let { it.endsWith("1103") || it.endsWith("2920") }
}.getOrNull() ?: false
/** 启动模块设置 [Activity] */
fun Context.startModuleSettings() {
/** 为 QQ、TIM 适配夜间模式 */
if (packageName == PackageName.QQ || packageName == PackageName.TIM)
AppCompatDelegate.setDefaultNightMode(if (isQQNightMode()) AppCompatDelegate.MODE_NIGHT_YES else AppCompatDelegate.MODE_NIGHT_NO)
startActivity(Intent(this, ConfigActivity::class.java))
}
/**
* 跳转模块设置 [Activity]
* @param isFinish 执行完成是否自动关闭当前活动
*/
fun Activity.jumpToModuleSettings(isFinish: Boolean = true) {
if (intent.hasExtra(JumpEvent.OPEN_MODULE_SETTING)) {
/** 宿主版本不匹配的时候自动结束宿主进程 */
if (intent.getLongExtra(JumpEvent.OPEN_MODULE_SETTING, 0) != YukiHookAPI.Status.compiledTimestamp)
exitProcess(status = 0)
intent.removeExtra(JumpEvent.OPEN_MODULE_SETTING)
startModuleSettings()
if (isFinish) finish()
}
}
/** Hook 系统电源锁 */
fun PackageParam.hookSystemWakeLock() {
PowerManager_WakeLockClass.hook {
injectMember {
method {
name = "acquireLocked"
emptyParam()
}
intercept()
}
}
}

View File

@@ -1,247 +0,0 @@
/*
* TSBattery - A new way to save your battery avoid cancer apps hacker it.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/TSBattery
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2021/9/4.
*/
@file:Suppress("SetTextI18n", "LocalVariableName", "SameParameterValue")
package com.fankes.tsbattery.ui
import android.content.ComponentName
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SwitchCompat
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.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_NOTIFY_TIP
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.factory.isInstall
import com.fankes.tsbattery.utils.factory.isNotSystemInDarkMode
import com.fankes.tsbattery.utils.factory.openSelfSetting
import com.fankes.tsbattery.utils.factory.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() {
companion object {
private const val moduleVersion = BuildConfig.VERSION_NAME
private const val qqSupportVersion =
"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.8.80、8.8.83 (8.2.11、8.5.5~8.8.83)"
private const val timSupportVersion = "2+、3+ (并未完全测试每个版本)"
private const val wechatSupportVersion = "全版本仅支持基础省电,更多功能依然画饼"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
/** 隐藏系统的标题栏 */
supportActionBar?.hide()
/** 初始化沉浸状态栏 */
immersionBar {
statusBarColor(R.color.colorThemeBackground)
autoDarkModeEnable(true)
statusBarDarkFont(isNotSystemInDarkMode)
navigationBarColor(R.color.colorThemeBackground)
navigationBarDarkIcon(isNotSystemInDarkMode)
fitsSystemWindows(true)
}
/** 判断 Hook 状态 */
if (isHooked()) {
findViewById<LinearLayout>(R.id.main_lin_status).setBackgroundResource(R.drawable.bg_green_round)
findViewById<ImageFilterView>(R.id.main_img_status).setImageResource(R.mipmap.ic_success)
findViewById<TextView>(R.id.main_text_status).text = "模块已激活"
/** 写入激活的模块版本 */
modulePrefs.putString(ENABLE_MODULE_VERSION, moduleVersion)
} else
showDialog {
title = "模块没有激活"
msg = "检测到模块没有激活,模块需要 Xposed 环境依赖," +
"同时需要系统拥有 Root 权限(太极阴可以免 Root)" +
"请自行查看本页面使用帮助与说明第三条。\n" +
"太极和第三方 Xposed 激活后" +
"可能不会提示激活,若想验证是否激活请打开“提示模块运行信息”自行检查," +
"或观察 QQ、TIM 的常驻通知是否有“TSBattery 守护中”字样”," +
"如果生效就代表模块运行正常,这里的激活状态只是一个显示意义上的存在。\n" +
"太极(无极)在 MIUI 设备上会提示打开授权,请进行允许,然后再次打开本应用查看激活状态。"
confirmButton(text = "我知道了")
noCancelable()
}
/** 推荐使用 LSPosed */
if (isTaiChiModuleActive)
showDialog {
title = "兼容性提示"
msg = "若你的设备已 Root推荐使用 LSPosed 激活模块,太极可能会出现模块设置无法保存的问题。"
confirmButton(text = "我知道了")
}
/** 检测应用转生 */
if (("com.bug.xposed").isInstall)
showDialog {
title = "环境异常"
msg = "检测到“应用转生”已被安装,为了保证模块的安全和稳定,请卸载更换其他 Hook 框架后才能继续使用。"
confirmButton(text = "退出") { finish() }
noCancelable()
}
/** 设置安装状态 */
findViewById<View>(R.id.main_text_qq_noinstall).isGone = QQ_PACKAGE_NAME.isInstall
findViewById<View>(R.id.main_text_tim_noinstall).isGone = TIM_PACKAGE_NAME.isInstall
findViewById<View>(R.id.main_text_wechat_noinstall).isGone = WECHAT_PACKAGE_NAME.isInstall
/** 设置文本 */
findViewById<TextView>(R.id.main_text_version).text = "模块版本:$moduleVersion"
findViewById<TextView>(R.id.main_text_support_qq).apply {
text = qqSupportVersion
setOnClickListener {
showDialog {
title = "兼容的 QQ 版本"
msg = qqSupportVersion
confirmButton(text = "我知道了")
}
}
}
findViewById<TextView>(R.id.main_text_support_tim).apply {
text = timSupportVersion
setOnClickListener {
showDialog {
title = "兼容的 TIM 版本"
msg = timSupportVersion
confirmButton(text = "我知道了")
}
}
}
findViewById<TextView>(R.id.main_text_support_wechat).apply {
text = wechatSupportVersion
setOnClickListener {
showDialog {
title = "兼容的微信版本"
msg = wechatSupportVersion
confirmButton(text = "我知道了")
}
}
}
/** 初始化 View */
val qqTimProtectModeSwitch = findViewById<SwitchCompat>(R.id.qqtim_protect_mode_switch)
val qqTimCoreServiceSwitch = findViewById<SwitchCompat>(R.id.shut_core_sv_qqtim_switch)
val qqTimCoreServiceKnSwitch = findViewById<SwitchCompat>(R.id.shut_core_sv_kn_qqtim_switch)
val wechatDisableHookSwitch = findViewById<SwitchCompat>(R.id.disable_wechat_sv_switch)
val hideIconInLauncherSwitch = findViewById<SwitchCompat>(R.id.hide_icon_in_launcher_switch)
val notifyModuleInfoSwitch = findViewById<SwitchCompat>(R.id.notify_module_info_switch)
val notifyNotifyTipSwitch = findViewById<SwitchCompat>(R.id.notify_module_notify_tip_switch)
/** 获取 Sp 存储的信息 */
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)
notifyNotifyTipSwitch.isChecked = modulePrefs.getBoolean(ENABLE_NOTIFY_TIP, default = true)
qqTimProtectModeSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(ENABLE_QQTIM_WHITE_MODE, b)
}
qqTimCoreServiceSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(ENABLE_QQTIM_CORESERVICE_BAN, b)
}
qqTimCoreServiceKnSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(ENABLE_QQTIM_CORESERVICE_CHILD_BAN, b)
}
wechatDisableHookSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(DISABLE_WECHAT_HOOK, b)
}
hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
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,
PackageManager.DONT_KILL_APP
)
}
notifyModuleInfoSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(ENABLE_RUN_INFO, b)
}
notifyNotifyTipSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(ENABLE_NOTIFY_TIP, b)
}
/** 快捷操作 QQ */
findViewById<View>(R.id.quick_qq_button).setOnClickListener { openSelfSetting(QQ_PACKAGE_NAME) }
/** 快捷操作 TIM */
findViewById<View>(R.id.quick_tim_button).setOnClickListener { openSelfSetting(TIM_PACKAGE_NAME) }
/** 快捷操作微信 */
findViewById<View>(R.id.quick_wechat_button).setOnClickListener { openSelfSetting(WECHAT_PACKAGE_NAME) }
/** 恰饭! */
findViewById<View>(R.id.link_with_follow_me).setOnClickListener {
runCatching {
startActivity(Intent().apply {
setPackage("com.coolapk.market")
action = "android.intent.action.VIEW"
data = Uri.parse("https://www.coolapk.com/u/876977")
/** 防止顶栈一样重叠在自己的 APP 中 */
flags = Intent.FLAG_ACTIVITY_NEW_TASK
})
}.onFailure {
Toast.makeText(this, "你可能没有安装酷安", Toast.LENGTH_SHORT).show()
}
}
/** 项目地址按钮点击事件 */
findViewById<View>(R.id.title_github_icon).setOnClickListener {
runCatching {
startActivity(Intent().apply {
action = "android.intent.action.VIEW"
data = Uri.parse("https://github.com/fankes/TSBattery")
/** 防止顶栈一样重叠在自己的 APP 中 */
flags = Intent.FLAG_ACTIVITY_NEW_TASK
})
}.onFailure {
Toast.makeText(this, "无法启动系统默认浏览器", Toast.LENGTH_SHORT).show()
}
}
}
/**
* 判断模块是否激活
* @return [Boolean] 激活状态
*/
private fun isHooked() = YukiHookModuleStatus.isActive() || isTaiChiModuleActive
}

View File

@@ -0,0 +1,174 @@
/*
* TSBattery - A new way to save your battery avoid cancer apps hacker it.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/TSBattery
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2021/9/4.
*/
@file:Suppress("SetTextI18n", "LocalVariableName", "SameParameterValue")
package com.fankes.tsbattery.ui.activity
import android.content.ComponentName
import android.content.Intent
import android.view.HapticFeedbackConstants
import androidx.core.view.isVisible
import com.fankes.tsbattery.BuildConfig
import com.fankes.tsbattery.R
import com.fankes.tsbattery.const.JumpEvent
import com.fankes.tsbattery.const.PackageName
import com.fankes.tsbattery.databinding.ActivityMainBinding
import com.fankes.tsbattery.hook.entity.QQTIMHooker
import com.fankes.tsbattery.hook.entity.WeChatHooker
import com.fankes.tsbattery.ui.activity.base.BaseActivity
import com.fankes.tsbattery.utils.factory.*
import com.fankes.tsbattery.utils.tool.GithubReleaseTool
import com.fankes.tsbattery.utils.tool.YukiPromoteTool
import com.highcapable.yukihookapi.YukiHookAPI
class MainActivity : BaseActivity<ActivityMainBinding>() {
companion object {
private val qqSupportVersions = arrayOf(
"8.0.0", "8.0.5", "8.0.7", "8.1.0", "8.1.3", "8.1.5", "8.1.8",
"8.2.0", "8.2.6", "8.2.7", "8.2.8", "8.2.11", "8.3.0", "8.3.5",
"8.3.6", "8.3.9", "8.4.1", "8.4.5", "8.4.8", "8.4.10", "8.4.17",
"8.4.18", "8.5.0", "8.5.5", "8.6.0", "8.6.5", "8.7.0", "8.7.5",
"8.7.8", "8.8.0", "8.8.3", "8.8.5", "8.8.11", "8.8.12", "8.8.17",
"8.8.20", "8.8.23", "8.8.28", "8.8.33", "8.8.35", "8.8.38", "8.8.50",
"8.8.55", "8.8.68", "8.8.80", "8.8.83", "8.8.85", "8.8.88", "8.8.90",
"8.8.93", "8.8.95", "8.8.98", "8.9.0", "8.9.1", "8.9.2", "8.9.3",
"8.9.5", "8.9.8", "8.9.10", "8.9.13", "8.9.15", "8.9.18", "8.9.19",
"8.9.20", "8.9.23", "8.9.25", "8.9.28"
)
private val qqSupportVersion by lazy {
if (qqSupportVersions.isNotEmpty()) {
var value = ""
qqSupportVersions.forEach { value += "$it" }
"${value.trim().let { it.substring(0, it.lastIndex) }}\n\n其余版本请自行测试是否有效。"
} else "empty"
}
private const val timSupportVersion = "2+、3+ (并未完全测试每个版本)。"
private const val wechatSupportVersion = "全版本仅支持基础省电,更多功能依然画饼。"
/** 预发布的版本标识 */
private const val pendingFlag = ""
}
override fun onCreate() {
/** 检查更新 */
GithubReleaseTool.checkingForUpdate(context = this, BuildConfig.VERSION_NAME) { version, function ->
binding.mainTextReleaseVersion.apply {
text = "点击更新 $version"
isVisible = true
setOnClickListener { function() }
}
}
/** 判断 Hook 状态 */
if (YukiHookAPI.Status.isModuleActive) {
binding.mainLinStatus.setBackgroundResource(R.drawable.bg_green_round)
binding.mainImgStatus.setImageResource(R.drawable.ic_success)
binding.mainTextStatus.text = "模块已激活"
binding.mainTextApiWay.isVisible = true
refreshActivateExecutor()
/** 推广、恰饭 */
YukiPromoteTool.promote(context = this)
} else
showDialog {
title = "模块没有激活"
msg = "检测到模块没有激活,若你正在使用免 Root 框架例如 LSPatch、太极或无极你可以忽略此提示。"
confirmButton(text = "我知道了")
noCancelable()
}
/** 设置安装状态 */
binding.mainTextQqVer.text = PackageName.QQ.takeIf { isInstall(it) }?.let { appVersionBrandOf(it) } ?: "未安装"
binding.mainTextTimVer.text = PackageName.TIM.takeIf { isInstall(it) }?.let { appVersionBrandOf(it) } ?: "未安装"
binding.mainTextWechatVer.text = PackageName.WECHAT.takeIf { isInstall(it) }?.let { appVersionBrandOf(it) } ?: "未安装"
/** 设置文本 */
binding.mainTextVersion.text = "模块版本:${BuildConfig.VERSION_NAME} $pendingFlag"
binding.mainQqItem.setOnClickListener {
showDialog {
title = "兼容的 QQ 版本"
msg = qqSupportVersion
confirmButton(text = "我知道了")
}
/** 振动提醒 */
it.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
}
binding.mainTimItem.setOnClickListener {
showDialog {
title = "兼容的 TIM 版本"
msg = timSupportVersion
confirmButton(text = "我知道了")
}
/** 振动提醒 */
it.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
}
binding.mainWechatItem.setOnClickListener {
showDialog {
title = "兼容的微信版本"
msg = wechatSupportVersion
confirmButton(text = "我知道了")
}
/** 振动提醒 */
it.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
}
/** 设置桌面图标显示隐藏 */
binding.hideIconInLauncherSwitch.isChecked = isLauncherIconShowing.not()
binding.hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b ->
if (btn.isPressed.not()) return@setOnCheckedChangeListener
hideOrShowLauncherIcon(b)
}
/** 快捷操作 QQ */
binding.quickQqButton.setOnClickListener { startModuleSettings(PackageName.QQ) }
/** 快捷操作 TIM */
binding.quickTimButton.setOnClickListener { startModuleSettings(PackageName.TIM) }
/** 快捷操作微信 */
binding.quickWechatButton.setOnClickListener { startModuleSettings(PackageName.WECHAT) }
/** 项目地址按钮点击事件 */
binding.titleGithubIcon.setOnClickListener { openBrowser(url = "https://github.com/fankes/TSBattery") }
/** 恰饭! */
binding.linkWithFollowMe.setOnClickListener {
openBrowser(url = "https://www.coolapk.com/u/876977", packageName = "com.coolapk.market")
}
}
/**
* 启动模块设置界面
* @param packageName 包名
*/
private fun startModuleSettings(packageName: String) {
if (isInstall(packageName)) runCatching {
startActivity(Intent().apply {
component = ComponentName(
packageName,
if (packageName != PackageName.WECHAT) QQTIMHooker.JumpActivityClass else WeChatHooker.LauncherUIClass
)
putExtra(JumpEvent.OPEN_MODULE_SETTING, YukiHookAPI.Status.compiledTimestamp)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
})
}.onFailure { snake(msg = "启动模块设置失败\n$it") } else snake(msg = "你没有安装此应用")
}
/** 刷新模块激活使用的方式 */
private fun refreshActivateExecutor() {
if (YukiHookAPI.Status.Executor.apiLevel > 0)
binding.mainTextApiWay.text = "Activated by ${YukiHookAPI.Status.Executor.name} API ${YukiHookAPI.Status.Executor.apiLevel}"
else binding.mainTextApiWay.text = "Activated by ${YukiHookAPI.Status.Executor.name}"
}
}

View File

@@ -0,0 +1,70 @@
/*
* TSBattery - A new way to save your battery avoid cancer apps hacker it.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/TSBattery
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/30.
*/
@file:Suppress("UNCHECKED_CAST")
package com.fankes.tsbattery.ui.activity.base
import android.os.Build
import android.os.Bundle
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.WindowCompat
import androidx.viewbinding.ViewBinding
import com.fankes.tsbattery.R
import com.fankes.tsbattery.utils.factory.isNotSystemInDarkMode
import com.highcapable.yukihookapi.hook.factory.current
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base.ModuleAppCompatActivity
abstract class BaseActivity<VB : ViewBinding> : ModuleAppCompatActivity() {
override val moduleTheme get() = R.style.Theme_TSBattery
/** 获取绑定布局对象 */
lateinit var binding: VB
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = current().generic()?.argument()?.method {
name = "inflate"
param(LayoutInflaterClass)
}?.get()?.invoke<VB>(layoutInflater) ?: error("binding failed")
setContentView(binding.root)
/** 隐藏系统的标题栏 */
supportActionBar?.hide()
/** 初始化沉浸状态栏 */
WindowCompat.getInsetsController(window, window.decorView).apply {
isAppearanceLightStatusBars = isNotSystemInDarkMode
isAppearanceLightNavigationBars = isNotSystemInDarkMode
}
ResourcesCompat.getColor(resources, R.color.colorThemeBackground, null).also {
window?.statusBarColor = it
window?.navigationBarColor = it
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) window?.navigationBarDividerColor = it
}
/** 装载子类 */
onCreate()
}
/** 回调 [onCreate] 方法 */
abstract fun onCreate()
}

View File

@@ -0,0 +1,140 @@
/*
* TSBattery - A new way to save your battery avoid cancer apps hacker it.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/TSBattery
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/9/28.
*/
@file:Suppress("SetTextI18n", "DEPRECATION")
package com.fankes.tsbattery.ui.activity.parasitic
import android.content.ComponentName
import android.content.Intent
import android.content.res.Resources
import android.widget.TextView
import androidx.core.view.isGone
import androidx.core.view.isVisible
import com.fankes.tsbattery.BuildConfig
import com.fankes.tsbattery.const.PackageName
import com.fankes.tsbattery.data.ConfigData
import com.fankes.tsbattery.data.ConfigData.bind
import com.fankes.tsbattery.databinding.ActivityConfigBinding
import com.fankes.tsbattery.hook.HookEntry
import com.fankes.tsbattery.hook.entity.QQTIMHooker
import com.fankes.tsbattery.ui.activity.MainActivity
import com.fankes.tsbattery.ui.activity.base.BaseActivity
import com.fankes.tsbattery.utils.factory.*
import com.fankes.tsbattery.utils.tool.GithubReleaseTool
import com.highcapable.yukihookapi.YukiHookAPI
import kotlin.system.exitProcess
class ConfigActivity : BaseActivity<ActivityConfigBinding>() {
override fun onCreate() {
/** 检查更新 */
GithubReleaseTool.checkingForUpdate(context = this, BuildConfig.VERSION_NAME) { version, function ->
binding.updateVersionText.apply {
text = "点击更新 $version"
isVisible = true
setOnClickListener { function() }
}
}
binding.titleBackIcon.setOnClickListener { finish() }
binding.titleModuleIcon.setOnClickListener {
showDialog {
title = "打开模块主界面"
msg = "点击确定后将打开模块主界面,如果未安装模块本体将会无法打开。"
confirmButton {
runCatching {
startActivity(Intent().apply {
component = ComponentName(BuildConfig.APPLICATION_ID, MainActivity::class.java.name)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
})
}.onFailure { snake(msg = "打开失败,请确认你已安装模块 APP\n$it") }
}
cancelButton()
}
}
binding.titleNameText.text = "TSBattery 设置 (${appName.trim()})"
binding.appIcon.setImageDrawable(appIconOf())
binding.appName.text = appName.trim()
binding.appVersion.text = "${appVersionName}($appVersionCode)"
binding.moduleVersion.text = "${BuildConfig.VERSION_NAME}(${BuildConfig.VERSION_CODE})"
binding.activeModeIcon.isVisible = HookEntry.isHookClientSupport
binding.inactiveModeIcon.isGone = HookEntry.isHookClientSupport
binding.unsupportItem.isGone = HookEntry.isHookClientSupport
binding.executorInfoText.text = "${YukiHookAPI.Status.Executor.name} API ${YukiHookAPI.Status.Executor.apiLevel}"
/** 刷新当前模式文本 */
fun refreshCurrentModeText() {
binding.currentModeText.text = when {
ConfigData.isDisableAllHook -> "模块已停用"
packageName == PackageName.WECHAT -> "基础省电模式"
ConfigData.isEnableQQTimProtectMode -> "保守模式"
else -> "完全模式"
}
}
refreshCurrentModeText()
/** 刷新配置条目显示隐藏状态 */
fun refreshConfigItems() {
binding.itemQqTimConfig.isVisible = packageName != PackageName.WECHAT && ConfigData.isDisableAllHook.not()
}
refreshConfigItems()
binding.infoTipText.replaceToAppName()
binding.qqTimProtectTipText.replaceToAppName()
binding.disableAllHookSwitch.bind(ConfigData.DISABLE_ALL_HOOK) { refreshConfigItems(); refreshCurrentModeText(); showRestartDialog() }
binding.qqTimProtectModeSwitch.bind(ConfigData.ENABLE_QQ_TIM_PROTECT_MODE) { refreshCurrentModeText(); showRestartDialog() }
binding.qqTimCoreServiceSwitch.bind(ConfigData.ENABLE_KILL_QQ_TIM_CORESERVICE) { showRestartDialog() }
binding.qqTimCoreServiceChildSwitch.bind(ConfigData.ENABLE_KILLE_QQ_TIM_CORESERVICE_CHILD) { showRestartDialog() }
}
/** 显示重新启动对话框 */
private fun showRestartDialog() {
showDialog {
title = "需要重新启动"
msg = "你必须重新启动${appName}才能使当前更改生效,现在重新启动吗?"
confirmButton {
cancel()
finish()
exitProcess(status = 0)
}
cancelButton(text = "稍后再说")
}
}
/** 替换占位符到当前 APP 名称 */
private fun TextView.replaceToAppName() {
text = text.toString().replace("{APP_NAME}", appName)
}
/** 重新设置 DPI 防止 QQ、TIM 修改它 */
override fun getResources(): Resources? = super.getResources().apply {
if (packageName == PackageName.QQ || packageName == PackageName.TIM)
QQTIMHooker.baseConfiguration?.also {
updateConfiguration(configuration.apply {
fontScale = it.fontScale
densityDpi = it.densityDpi
}, displayMetrics)
}
}
/**
* 获取当前 APP 名称
* @return [String]
*/
private val appName by lazy { appNameOf().let { if (packageName == PackageName.WECHAT) it else " $it " } }
}

View File

@@ -21,15 +21,17 @@
*/
@file:Suppress("SameParameterValue")
package com.fankes.tsbattery.view
package com.fankes.tsbattery.ui.view
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.text.TextUtils
import android.util.AttributeSet
import androidx.appcompat.widget.SwitchCompat
import com.fankes.tsbattery.utils.drawable.drawabletoolbox.DrawableBuilder
import com.fankes.tsbattery.utils.factory.dp
import com.fankes.tsbattery.utils.factory.isSystemInDarkMode
import top.defaults.drawabletoolbox.DrawableBuilder
class MaterialSwitch(context: Context, attrs: AttributeSet?) : SwitchCompat(context, attrs) {
@@ -42,6 +44,8 @@ class MaterialSwitch(context: Context, attrs: AttributeSet?) : SwitchCompat(cont
return ColorStateList(states, colors)
}
private val thumbColor get() = if (context.isSystemInDarkMode) 0xFF7C7C7C else 0xFFCCCCCC
init {
trackDrawable = DrawableBuilder()
.rectangle()
@@ -61,8 +65,10 @@ class MaterialSwitch(context: Context, attrs: AttributeSet?) : SwitchCompat(cont
.build()
trackTintList = toColors(
0xFF656565.toInt(),
0xFFCCCCCC.toInt(),
0xFFCCCCCC.toInt()
thumbColor.toInt(),
thumbColor.toInt()
)
isSingleLine = true
ellipsize = TextUtils.TruncateAt.END
}
}

View File

@@ -1,292 +0,0 @@
/*
* TSBattery - A new way to save your battery avoid cancer apps hacker it.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/TSBattery
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
*/
@file:Suppress("SameParameterValue")
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
import android.annotation.SuppressLint
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.RippleDrawable
import android.graphics.drawable.RotateDrawable
import android.os.Build
import java.lang.reflect.Field
import java.lang.reflect.Method
private val gradientState = resolveGradientState()
private fun resolveGradientState(): Class<*> {
val classes = GradientDrawable::class.java.declaredClasses
for (singleClass in classes) {
if (singleClass.simpleName == "GradientState") return singleClass
}
throw RuntimeException("GradientState could not be found in thisAny GradientDrawable implementation")
}
private val rotateState = resolveRotateState()
private fun resolveRotateState(): Class<*> {
val classes = RotateDrawable::class.java.declaredClasses
for (singleClass in classes) {
if (singleClass.simpleName == "RotateState") return singleClass
}
throw RuntimeException("RotateState could not be found in thisAny RotateDrawable implementation")
}
@Throws(SecurityException::class, NoSuchFieldException::class)
private fun resolveField(source: Class<*>, fieldName: String): Field {
val field = source.getDeclaredField(fieldName)
field.isAccessible = true
return field
}
@Throws(SecurityException::class, NoSuchMethodException::class)
private fun resolveMethod(
source: Class<*>,
methodName: String,
vararg parameterTypes: Class<*>
): Method {
val method = source.getDeclaredMethod(methodName, *parameterTypes)
method.isAccessible = true
return method
}
fun setInnerRadius(drawable: GradientDrawable, value: Int) {
try {
val innerRadius = resolveField(gradientState, "mInnerRadius")
innerRadius.setInt(drawable.constantState, value)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
fun setInnerRadiusRatio(drawable: GradientDrawable, value: Float) {
try {
val innerRadius = resolveField(gradientState, "mInnerRadiusRatio")
innerRadius.setFloat(drawable.constantState, value)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
fun setThickness(drawable: GradientDrawable, value: Int) {
try {
val innerRadius = resolveField(gradientState, "mThickness")
innerRadius.setInt(drawable.constantState, value)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
fun setThicknessRatio(drawable: GradientDrawable, value: Float) {
try {
val innerRadius = resolveField(gradientState, "mThicknessRatio")
innerRadius.setFloat(drawable.constantState, value)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
fun setUseLevelForShape(drawable: GradientDrawable, value: Boolean) {
try {
val useLevelForShape = resolveField(gradientState, "mUseLevelForShape")
useLevelForShape.setBoolean(drawable.constantState, value)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
@SuppressLint("ObsoleteSdkInt")
fun setOrientation(drawable: GradientDrawable, value: GradientDrawable.Orientation) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
drawable.orientation = value
} else {
try {
val orientation = resolveField(gradientState, "mOrientation")
orientation.set(drawable.constantState, value)
val rectIdDirty = resolveField(GradientDrawable::class.java, "mRectIsDirty")
rectIdDirty.setBoolean(drawable, true)
drawable.invalidateSelf()
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
}
@SuppressLint("ObsoleteSdkInt")
fun setColors(drawable: GradientDrawable, value: IntArray) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
drawable.colors = value
} else {
try {
val colors = resolveField(gradientState, "mColors")
colors.set(drawable.constantState, value)
drawable.invalidateSelf()
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
}
fun setGradientRadiusType(drawable: GradientDrawable, value: Int) {
try {
val type = resolveField(gradientState, "mGradientRadiusType")
type.setInt(drawable.constantState, value)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
fun setGradientRadius(drawable: GradientDrawable, value: Float) {
try {
val gradientRadius = resolveField(gradientState, "mGradientRadius")
gradientRadius.setFloat(drawable.constantState, value)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
fun setStrokeColor(drawable: GradientDrawable, value: Int) {
try {
val type = resolveField(gradientState, "mStrokeColor")
type.setInt(drawable.constantState, value)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
fun setDrawable(rotateDrawable: RotateDrawable, drawable: Drawable) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
rotateDrawable.drawable = drawable
} else {
try {
val drawableField = resolveField(rotateState, "mDrawable")
val stateField = resolveField(RotateDrawable::class.java, "mState")
drawableField.set(stateField.get(rotateDrawable), drawable)
drawable.callback = rotateDrawable
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
}
fun setPivotX(rotateDrawable: RotateDrawable, value: Float) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
rotateDrawable.pivotX = value
} else {
try {
val pivotXField = resolveField(rotateState, "mPivotX")
pivotXField.setFloat(rotateDrawable.constantState, value)
val pivotXRelField = resolveField(rotateState, "mPivotXRel")
pivotXRelField.setBoolean(rotateDrawable.constantState, true)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
}
fun setPivotY(rotateDrawable: RotateDrawable, value: Float) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
rotateDrawable.pivotY = value
} else {
try {
val pivotYField = resolveField(rotateState, "mPivotY")
pivotYField.setFloat(rotateDrawable.constantState, value)
val pivotYRelField = resolveField(rotateState, "mPivotYRel")
pivotYRelField.setBoolean(rotateDrawable.constantState, true)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
}
fun setFromDegrees(rotateDrawable: RotateDrawable, value: Float) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
rotateDrawable.fromDegrees = value
} else {
try {
val fromDegreesField = resolveField(rotateState, "mFromDegrees")
fromDegreesField.setFloat(rotateDrawable.constantState, value)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
}
fun setToDegrees(rotateDrawable: RotateDrawable, value: Float) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
rotateDrawable.toDegrees = value
} else {
try {
val toDegreesField = resolveField(rotateState, "mToDegrees")
toDegreesField.setFloat(rotateDrawable.constantState, value)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
}
fun setRadius(rippleDrawable: RippleDrawable, value: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
rippleDrawable.radius = value
} else {
try {
val setRadiusMethod =
resolveMethod(RippleDrawable::class.java, "setMaxRadius", Int::class.java)
setRadiusMethod.invoke(rippleDrawable, value)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
}

View File

@@ -1,29 +0,0 @@
/*
* TSBattery - A new way to save your battery avoid cancer apps hacker it.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/TSBattery
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
*/
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
class Constants {
companion object {
const val DEFAULT_COLOR = 0xFFBA68C8.toInt()
}
}

View File

@@ -1,489 +0,0 @@
/*
* TSBattery - A new way to save your battery avoid cancer apps hacker it.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/TSBattery
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
*/
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
import android.content.res.ColorStateList
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.util.StateSet
import java.util.*
import java.util.concurrent.atomic.AtomicInteger
class DrawableBuilder {
private var properties = DrawableProperties()
private var order: AtomicInteger = AtomicInteger(1)
private var transformsMap = TreeMap<Int, (Drawable) -> Drawable>()
private var baseDrawable: Drawable? = null
fun batch(properties: DrawableProperties) = apply { this.properties = properties.copy() }
fun baseDrawable(drawable: Drawable) = apply { baseDrawable = drawable }
// <shape>
fun shape(shape: Int) = apply { properties.shape = shape }
fun rectangle() = apply { shape(GradientDrawable.RECTANGLE) }
fun oval() = apply { shape(GradientDrawable.OVAL) }
fun line() = apply { shape(GradientDrawable.LINE) }
fun ring() = apply { shape(GradientDrawable.RING) }
fun innerRadius(innerRadius: Int) = apply { properties.innerRadius = innerRadius }
fun innerRadiusRatio(innerRadiusRatio: Float) =
apply { properties.innerRadiusRatio = innerRadiusRatio }
fun thickness(thickness: Int) = apply { properties.thickness = thickness }
fun thicknessRatio(thicknessRatio: Float) = apply { properties.thicknessRatio = thicknessRatio }
fun useLevelForRing(use: Boolean = true) = apply { properties.useLevelForRing = use }
// <corner>
fun cornerRadius(cornerRadius: Int) = apply { properties.cornerRadius = cornerRadius }
fun topLeftRadius(topLeftRadius: Int) = apply { properties.topLeftRadius = topLeftRadius }
fun topRightRadius(topRightRadius: Int) = apply { properties.topRightRadius = topRightRadius }
fun bottomRightRadius(bottomRightRadius: Int) =
apply { properties.bottomRightRadius = bottomRightRadius }
fun bottomLeftRadius(bottomLeftRadius: Int) =
apply { properties.bottomLeftRadius = bottomLeftRadius }
fun rounded() = apply { cornerRadius(Int.MAX_VALUE) }
fun cornerRadii(
topLeftRadius: Int,
topRightRadius: Int,
bottomRightRadius: Int,
bottomLeftRadius: Int
) = apply {
topLeftRadius(topLeftRadius); topRightRadius(topRightRadius); bottomRightRadius(
bottomRightRadius
); bottomLeftRadius(bottomLeftRadius)
}
// <gradient>
fun gradient(useGradient: Boolean = true) = apply { properties.useGradient = useGradient }
fun gradientType(type: Int) = apply { properties.type = type }
fun linearGradient() = apply { gradientType(GradientDrawable.LINEAR_GRADIENT) }
fun radialGradient() = apply { gradientType(GradientDrawable.RADIAL_GRADIENT) }
fun sweepGradient() = apply { gradientType(GradientDrawable.SWEEP_GRADIENT) }
fun angle(angle: Int) = apply { properties.angle = angle }
fun centerX(centerX: Float) = apply { properties.centerX = centerX }
fun centerY(centerY: Float) = apply { properties.centerY = centerY }
fun center(centerX: Float, centerY: Float) = apply { centerX(centerX); centerY(centerY) }
fun useCenterColor(useCenterColor: Boolean = true) =
apply { properties.useCenterColor = useCenterColor }
fun startColor(startColor: Int) = apply { properties.startColor = startColor }
fun centerColor(centerColor: Int) = apply {
properties.centerColor = centerColor
useCenterColor(true)
}
fun endColor(endColor: Int) = apply { properties.endColor = endColor }
fun gradientColors(startColor: Int, endColor: Int, centerColor: Int?) = apply {
startColor(startColor); endColor(endColor)
useCenterColor(centerColor != null)
centerColor?.let {
centerColor(it)
}
}
fun gradientRadiusType(gradientRadiusType: Int) =
apply { properties.gradientRadiusType = gradientRadiusType }
fun gradientRadius(gradientRadius: Float) = apply { properties.gradientRadius = gradientRadius }
fun gradientRadius(radius: Float, type: Int) =
apply { gradientRadius(radius); gradientRadiusType(type) }
fun gradientRadiusInPixel(radius: Float) =
apply { gradientRadius(radius); gradientRadiusType(DrawableProperties.RADIUS_TYPE_PIXELS) }
fun gradientRadiusInFraction(radius: Float) =
apply { gradientRadius(radius); gradientRadiusType(DrawableProperties.RADIUS_TYPE_FRACTION) }
fun useLevelForGradient(use: Boolean) = apply { properties.useLevelForGradient = use }
fun useLevelForGradient() = apply { useLevelForGradient(true) }
// <size>
fun width(width: Int) = apply { properties.width = width }
fun height(height: Int) = apply { properties.height = height }
fun size(width: Int, height: Int) = apply { width(width); height(height) }
fun size(size: Int) = apply { width(size).height(size) }
// <solid>
fun solidColor(solidColor: Int) = apply { properties.solidColor = solidColor }
private var solidColorPressed: Int? = null
fun solidColorPressed(color: Int?) = apply { solidColorPressed = color }
private var solidColorPressedWhenRippleUnsupported: Int? = null
fun solidColorPressedWhenRippleUnsupported(color: Int?) =
apply { solidColorPressedWhenRippleUnsupported = color }
private var solidColorDisabled: Int? = null
fun solidColorDisabled(color: Int?) = apply { solidColorDisabled = color }
private var solidColorSelected: Int? = null
fun solidColorSelected(color: Int?) = apply { solidColorSelected = color }
fun solidColorStateList(colorStateList: ColorStateList) =
apply { properties.solidColorStateList = colorStateList }
// <stroke>
fun strokeWidth(strokeWidth: Int) = apply { properties.strokeWidth = strokeWidth }
fun strokeColor(strokeColor: Int) = apply { properties.strokeColor = strokeColor }
private var strokeColorPressed: Int? = null
fun strokeColorPressed(color: Int?) = apply { strokeColorPressed = color }
private var strokeColorDisabled: Int? = null
fun strokeColorDisabled(color: Int?) = apply { strokeColorDisabled = color }
private var strokeColorSelected: Int? = null
fun strokeColorSelected(color: Int?) = apply { strokeColorSelected = color }
fun strokeColorStateList(colorStateList: ColorStateList) =
apply { properties.strokeColorStateList = colorStateList }
fun dashWidth(dashWidth: Int) = apply { properties.dashWidth = dashWidth }
fun dashGap(dashGap: Int) = apply { properties.dashGap = dashGap }
fun hairlineBordered() = apply { strokeWidth(1) }
fun shortDashed() = apply { dashWidth(12).dashGap(12) }
fun mediumDashed() = apply { dashWidth(24).dashGap(24) }
fun longDashed() = apply { dashWidth(36).dashGap(36) }
fun dashed() = apply { mediumDashed() }
// <rotate>
private var rotateOrder = 0
fun rotate(boolean: Boolean = true) = apply {
properties.useRotate = boolean
rotateOrder = if (boolean) {
order.getAndIncrement()
} else {
0
}
}
fun pivotX(pivotX: Float) = apply { properties.pivotX = pivotX }
fun pivotY(pivotY: Float) = apply { properties.pivotY = pivotY }
fun pivot(pivotX: Float, pivotY: Float) = apply { pivotX(pivotX).pivotY(pivotY) }
fun fromDegrees(degrees: Float) = apply { properties.fromDegrees = degrees }
fun toDegrees(degrees: Float) = apply { properties.toDegrees = degrees }
fun degrees(fromDegrees: Float, toDegrees: Float) =
apply { fromDegrees(fromDegrees).toDegrees(toDegrees) }
fun degrees(degrees: Float) = apply { fromDegrees(degrees).toDegrees(degrees) }
fun rotate(fromDegrees: Float, toDegrees: Float) =
apply { rotate().fromDegrees(fromDegrees).toDegrees(toDegrees) }
fun rotate(degrees: Float) = apply { rotate().degrees(degrees) }
// <scale>
private var scaleOrder = 0
fun scale(boolean: Boolean = true) = apply {
properties.useScale = boolean
scaleOrder = if (boolean) {
order.getAndIncrement()
} else {
0
}
}
fun scaleLevel(level: Int) = apply { properties.scaleLevel = level }
fun scaleGravity(gravity: Int) = apply { properties.scaleGravity = gravity }
fun scaleWidth(scale: Float) = apply { properties.scaleWidth = scale }
fun scaleHeight(scale: Float) = apply { properties.scaleHeight = scale }
fun scale(scale: Float) = apply { scale().scaleWidth(scale).scaleHeight(scale) }
fun scale(scaleWidth: Float, scaleHeight: Float) =
apply { scale().scaleWidth(scaleWidth).scaleHeight(scaleHeight) }
// flip
fun flip(boolean: Boolean = true) = apply { properties.useFlip = boolean }
fun orientation(orientation: Int) = apply { properties.orientation = orientation }
fun flipVertical() = apply { flip().orientation(FlipDrawable.ORIENTATION_VERTICAL) }
// <ripple>
fun ripple(boolean: Boolean = true) = apply { properties.useRipple = boolean }
fun rippleColor(color: Int) = apply { properties.rippleColor = color }
fun rippleColorStateList(colorStateList: ColorStateList) =
apply { properties.rippleColorStateList = colorStateList }
fun rippleRadius(radius: Int) = apply { properties.rippleRadius = radius }
fun build(): Drawable {
if (baseDrawable != null) {
return wrap(baseDrawable!!)
}
var drawable: Drawable
// fall back when ripple is unavailable on devices with API < 21
if (shouldFallbackRipple()) {
if (solidColorPressedWhenRippleUnsupported != null) {
solidColorPressed(solidColorPressedWhenRippleUnsupported)
} else {
solidColorPressed(properties.rippleColor)
}
}
if (needStateListDrawable()) {
drawable = StateListDrawableBuilder()
.pressed(buildPressedDrawable())
.disabled(buildDisabledDrawable())
.selected(buildSelectedDrawable())
.normal(buildNormalDrawable())
.build()
} else {
drawable = GradientDrawable()
setupGradientDrawable(drawable)
}
drawable = wrap(drawable)
return drawable
}
private fun getSolidColorStateList(): ColorStateList {
if (properties.solidColorStateList != null) {
return properties.solidColorStateList!!
}
val states = mutableListOf<IntArray>()
val colors = mutableListOf<Int>()
solidColorPressed?.let {
states.add(intArrayOf(android.R.attr.state_pressed))
colors.add(it)
}
solidColorDisabled?.let {
states.add(intArrayOf(-android.R.attr.state_enabled))
colors.add(it)
}
solidColorSelected?.let {
states.add(intArrayOf(android.R.attr.state_selected))
colors.add(it)
}
states.add(StateSet.WILD_CARD)
colors.add(properties.solidColor)
return ColorStateList(states.toTypedArray(), colors.toIntArray())
}
private fun getStrokeColorStateList(): ColorStateList {
if (properties.strokeColorStateList != null) {
return properties.strokeColorStateList!!
}
val states = mutableListOf<IntArray>()
val colors = mutableListOf<Int>()
strokeColorPressed?.let {
states.add(intArrayOf(android.R.attr.state_pressed))
colors.add(it)
}
strokeColorDisabled?.let {
states.add(intArrayOf(-android.R.attr.state_enabled))
colors.add(it)
}
strokeColorSelected?.let {
states.add(intArrayOf(android.R.attr.state_selected))
colors.add(it)
}
states.add(StateSet.WILD_CARD)
colors.add(properties.strokeColor)
return ColorStateList(states.toTypedArray(), colors.toIntArray())
}
private fun buildPressedDrawable(): Drawable? {
if (solidColorPressed == null && strokeColorPressed == null) return null
val pressedDrawable = GradientDrawable()
setupGradientDrawable(pressedDrawable)
solidColorPressed?.let {
pressedDrawable.setColor(it)
}
strokeColorPressed?.let {
setStrokeColor(pressedDrawable, it)
}
return pressedDrawable
}
private fun buildDisabledDrawable(): Drawable? {
if (solidColorDisabled == null && strokeColorDisabled == null) return null
val disabledDrawable = GradientDrawable()
setupGradientDrawable(disabledDrawable)
solidColorDisabled?.let {
disabledDrawable.setColor(it)
}
strokeColorDisabled?.let {
setStrokeColor(disabledDrawable, it)
}
return disabledDrawable
}
private fun buildSelectedDrawable(): Drawable? {
if (solidColorSelected == null && strokeColorSelected == null) return null
val selectedDrawable = GradientDrawable()
setupGradientDrawable(selectedDrawable)
solidColorSelected?.let {
selectedDrawable.setColor(it)
}
strokeColorSelected?.let {
setStrokeColor(selectedDrawable, it)
}
return selectedDrawable
}
private fun buildNormalDrawable(): Drawable {
val pressedDrawable = GradientDrawable()
setupGradientDrawable(pressedDrawable)
return pressedDrawable
}
private fun setupGradientDrawable(drawable: GradientDrawable) {
properties.apply {
drawable.shape = shape
if (shape == GradientDrawable.RING) {
setInnerRadius(drawable, innerRadius)
setInnerRadiusRatio(drawable, innerRadiusRatio)
setThickness(drawable, thickness)
setThicknessRatio(drawable, thicknessRatio)
setUseLevelForShape(drawable, useLevelForRing)
}
drawable.cornerRadii = getCornerRadii()
if (useGradient) {
drawable.gradientType = type
setGradientRadiusType(drawable, gradientRadiusType)
setGradientRadius(drawable, gradientRadius)
drawable.setGradientCenter(centerX, centerY)
setOrientation(drawable, getOrientation())
setColors(drawable, getColors())
drawable.useLevel = useLevelForGradient
} else {
drawable.color = getSolidColorStateList()
}
drawable.setSize(width, height)
drawable.setStroke(
strokeWidth,
getStrokeColorStateList(),
dashWidth.toFloat(),
dashGap.toFloat()
)
}
}
private fun needStateListDrawable(): Boolean {
return (hasStrokeColorStateList() || (!properties.useGradient && hasSolidColorStateList()))
}
private fun needRotateDrawable(): Boolean {
return properties.useRotate &&
!(properties.pivotX == 0.5f && properties.pivotY == 0.5f
&& properties.fromDegrees == 0f && properties.toDegrees == 0f)
}
private fun needScaleDrawable(): Boolean {
return properties.useScale
}
private fun wrap(drawable: Drawable): Drawable {
var wrappedDrawable = drawable
if (rotateOrder > 0) {
transformsMap[rotateOrder] = ::wrapRotateIfNeeded
}
if (scaleOrder > 0) {
transformsMap[scaleOrder] = ::wrapScaleIfNeeded
}
for (action in transformsMap.values) {
wrappedDrawable = action.invoke(wrappedDrawable)
}
if (properties.useFlip) {
wrappedDrawable = FlipDrawableBuilder()
.drawable(wrappedDrawable)
.orientation(properties.orientation)
.build()
}
if (isRippleSupported() && properties.useRipple) {
wrappedDrawable = RippleDrawableBuilder()
.drawable(wrappedDrawable)
.color(properties.rippleColor)
.colorStateList(properties.rippleColorStateList)
.radius(properties.rippleRadius)
.build()
}
return wrappedDrawable
}
private fun shouldFallbackRipple(): Boolean {
return properties.useRipple && !isRippleSupported()
}
private fun isRippleSupported() = true
private fun wrapRotateIfNeeded(drawable: Drawable): Drawable {
if (!needRotateDrawable()) return drawable
with(properties) {
return RotateDrawableBuilder()
.drawable(drawable)
.pivotX(pivotX)
.pivotY(pivotY)
.fromDegrees(fromDegrees)
.toDegrees(toDegrees)
.build()
}
}
private fun wrapScaleIfNeeded(drawable: Drawable): Drawable {
if (!needScaleDrawable()) return drawable
with(properties) {
return ScaleDrawableBuilder()
.drawable(drawable)
.level(scaleLevel)
.scaleGravity(scaleGravity)
.scaleWidth(scaleWidth)
.scaleHeight(scaleHeight)
.build()
}
}
private fun hasSolidColorStateList(): Boolean {
return solidColorPressed != null || solidColorDisabled != null || solidColorSelected != null
}
private fun hasStrokeColorStateList(): Boolean {
return strokeColorPressed != null || strokeColorDisabled != null || strokeColorSelected != null
}
}

View File

@@ -1,221 +0,0 @@
/*
* TSBattery - A new way to save your battery avoid cancer apps hacker it.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/TSBattery
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
*/
@file:Suppress("SetterBackingFieldAssignment", "unused")
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.os.Parcel
import android.os.Parcelable
import android.view.Gravity
import java.io.Serializable
data class DrawableProperties(
// <shape>
@JvmField var shape: Int = GradientDrawable.RECTANGLE,
@JvmField var innerRadius: Int = -1,
@JvmField var innerRadiusRatio: Float = 9f,
@JvmField var thickness: Int = -1,
@JvmField var thicknessRatio: Float = 3f,
@JvmField var useLevelForRing: Boolean = false,
// <corner>
private var _cornerRadius: Int = 0,
@JvmField var topLeftRadius: Int = 0,
@JvmField var topRightRadius: Int = 0,
@JvmField var bottomRightRadius: Int = 0,
@JvmField var bottomLeftRadius: Int = 0,
// <gradient>
@JvmField var useGradient: Boolean = false,
@JvmField var type: Int = GradientDrawable.RADIAL_GRADIENT,
@JvmField var angle: Int = 0,
@JvmField var centerX: Float = 0.5f,
@JvmField var centerY: Float = 0.5f,
@JvmField var useCenterColor: Boolean = false,
@JvmField var startColor: Int = Constants.DEFAULT_COLOR,
@JvmField var centerColor: Int? = null,
@JvmField var endColor: Int = 0x7FFFFFFF,
@JvmField var gradientRadiusType: Int = RADIUS_TYPE_FRACTION,
@JvmField var gradientRadius: Float = 0.5f,
@JvmField var useLevelForGradient: Boolean = false,
// <size>
@JvmField var width: Int = -1,
@JvmField var height: Int = -1,
// <solid>
@JvmField var solidColor: Int = Color.TRANSPARENT,
@JvmField var solidColorStateList: ColorStateList? = null,
// <stroke>
@JvmField var strokeWidth: Int = 0,
@JvmField var strokeColor: Int = Color.DKGRAY,
@JvmField var strokeColorStateList: ColorStateList? = null,
@JvmField var dashWidth: Int = 0,
@JvmField var dashGap: Int = 0,
// <rotate>
@JvmField var useRotate: Boolean = false,
@JvmField var pivotX: Float = 0.5f,
@JvmField var pivotY: Float = 0.5f,
@JvmField var fromDegrees: Float = 0f,
@JvmField var toDegrees: Float = 0f,
// <scale>
@JvmField var useScale: Boolean = false,
@JvmField var scaleLevel: Int = 10000,
@JvmField var scaleGravity: Int = Gravity.CENTER,
@JvmField var scaleWidth: Float = 0f,
@JvmField var scaleHeight: Float = 0f,
// flip
@JvmField var useFlip: Boolean = false,
@JvmField var orientation: Int = FlipDrawable.ORIENTATION_HORIZONTAL,
// ripple
@JvmField var useRipple: Boolean = false,
@JvmField var rippleColor: Int = Constants.DEFAULT_COLOR,
@JvmField var rippleColorStateList: ColorStateList? = null,
@JvmField var rippleRadius: Int = -1
) : Serializable {
companion object {
const val RADIUS_TYPE_PIXELS = 0
const val RADIUS_TYPE_FRACTION = 1
@JvmField
val CREATOR = object : Parcelable.Creator<DrawableProperties> {
override fun createFromParcel(parcel: Parcel): DrawableProperties {
return DrawableProperties(parcel)
}
override fun newArray(size: Int): Array<DrawableProperties?> {
return arrayOfNulls(size)
}
}
}
var cornerRadius: Int = _cornerRadius
set(value) {
_cornerRadius = value
topLeftRadius = value
topRightRadius = value
bottomRightRadius = value
bottomLeftRadius = value
}
constructor(parcel: Parcel) : this(
parcel.readInt(),
parcel.readInt(),
parcel.readFloat(),
parcel.readInt(),
parcel.readFloat(),
parcel.readByte() != 0.toByte(),
parcel.readInt(),
parcel.readInt(),
parcel.readInt(),
parcel.readInt(),
parcel.readInt(),
parcel.readByte() != 0.toByte(),
parcel.readInt(),
parcel.readInt(),
parcel.readFloat(),
parcel.readFloat(),
parcel.readByte() != 0.toByte(),
parcel.readInt(),
parcel.readValue(Int::class.java.classLoader) as? Int,
parcel.readInt(),
parcel.readInt(),
parcel.readFloat(),
parcel.readByte() != 0.toByte(),
parcel.readInt(),
parcel.readInt(),
parcel.readInt(),
parcel.readParcelable(ColorStateList::class.java.classLoader),
parcel.readInt(),
parcel.readInt(),
parcel.readParcelable(ColorStateList::class.java.classLoader),
parcel.readInt(),
parcel.readInt(),
parcel.readByte() != 0.toByte(),
parcel.readFloat(),
parcel.readFloat(),
parcel.readFloat(),
parcel.readFloat(),
parcel.readByte() != 0.toByte(),
parcel.readInt(),
parcel.readInt(),
parcel.readFloat(),
parcel.readFloat(),
parcel.readByte() != 0.toByte(),
parcel.readInt(),
parcel.readByte() != 0.toByte(),
parcel.readInt(),
parcel.readParcelable(ColorStateList::class.java.classLoader),
parcel.readInt()
)
fun copy(): DrawableProperties {
val parcel = Parcel.obtain()
parcel.setDataPosition(0)
val properties = CREATOR.createFromParcel(parcel)
parcel.recycle()
return properties
}
fun getCornerRadii(): FloatArray {
return floatArrayOf(
topLeftRadius.toFloat(), topLeftRadius.toFloat(),
topRightRadius.toFloat(), topRightRadius.toFloat(),
bottomRightRadius.toFloat(), bottomRightRadius.toFloat(),
bottomLeftRadius.toFloat(), bottomLeftRadius.toFloat()
)
}
fun getOrientation(): GradientDrawable.Orientation {
val orientation: GradientDrawable.Orientation = when (val angle = this.angle % 360) {
0 -> GradientDrawable.Orientation.LEFT_RIGHT
45 -> GradientDrawable.Orientation.BL_TR
90 -> GradientDrawable.Orientation.BOTTOM_TOP
135 -> GradientDrawable.Orientation.BR_TL
180 -> GradientDrawable.Orientation.RIGHT_LEFT
225 -> GradientDrawable.Orientation.TR_BL
270 -> GradientDrawable.Orientation.TOP_BOTTOM
315 -> GradientDrawable.Orientation.TL_BR
else -> throw IllegalArgumentException("Unsupported angle: $angle")
}
return orientation
}
fun getColors(): IntArray {
return if (useCenterColor && centerColor != null) {
intArrayOf(startColor, centerColor!!, endColor)
} else intArrayOf(startColor, endColor)
}
fun materialization(): Drawable = DrawableBuilder().batch(this).build()
}

View File

@@ -1,78 +0,0 @@
/*
* TSBattery - A new way to save your battery avoid cancer apps hacker it.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/TSBattery
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
*/
@file:Suppress("DEPRECATION", "CanvasSize")
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.Rect
import android.graphics.drawable.Drawable
class FlipDrawable(
private var drawable: Drawable,
private var orientation: Int = ORIENTATION_HORIZONTAL
) : Drawable() {
companion object {
const val ORIENTATION_HORIZONTAL = 0
const val ORIENTATION_VERTICAL = 1
}
override fun draw(canvas: Canvas) {
val saveCount = canvas.save()
if (orientation == ORIENTATION_VERTICAL) {
canvas.scale(1f, -1f, (canvas.width / 2).toFloat(), (canvas.height / 2).toFloat())
} else {
canvas.scale(-1f, 1f, (canvas.width / 2).toFloat(), (canvas.height / 2).toFloat())
}
drawable.bounds = Rect(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
canvas.restoreToCount(saveCount)
}
override fun onLevelChange(level: Int): Boolean {
drawable.level = level
invalidateSelf()
return true
}
override fun getIntrinsicWidth(): Int {
return drawable.intrinsicWidth
}
override fun getIntrinsicHeight(): Int {
return drawable.intrinsicHeight
}
override fun setAlpha(alpha: Int) {
drawable.alpha = alpha
}
override fun getOpacity(): Int {
return drawable.opacity
}
override fun setColorFilter(colorFilter: ColorFilter?) {
drawable.colorFilter = colorFilter
}
}

View File

@@ -1,35 +0,0 @@
/*
* TSBattery - A new way to save your battery avoid cancer apps hacker it.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/TSBattery
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
*/
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
import android.graphics.drawable.Drawable
class FlipDrawableBuilder : DrawableWrapperBuilder<FlipDrawableBuilder>() {
private var orientation: Int = FlipDrawable.ORIENTATION_HORIZONTAL
fun orientation(orientation: Int) = apply { this.orientation = orientation }
override fun build(): Drawable {
return FlipDrawable(drawable!!, orientation)
}
}

View File

@@ -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.
*
* 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
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
*/
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.os.Build
import android.view.Gravity
import androidx.annotation.RequiresApi
import java.util.*
class LayerDrawableBuilder {
companion object {
const val DIMEN_UNDEFINED = Int.MIN_VALUE
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private var paddingMode = LayerDrawable.PADDING_MODE_NEST
private var paddingLeft = 0
private var paddingTop = 0
private var paddingRight = 0
private var paddingBottom = 0
private var paddingStart = 0
private var paddingEnd = 0
private val layers = ArrayList<Layer>()
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun paddingMode(mode: Int) = apply { paddingMode = mode }
fun paddingLeft(padding: Int) = apply { paddingLeft = padding }
fun paddingTop(padding: Int) = apply { paddingTop = padding }
fun paddingRight(padding: Int) = apply { paddingRight = padding }
fun paddingBottom(padding: Int) = apply { paddingBottom = padding }
@RequiresApi(Build.VERSION_CODES.M)
fun paddingStart(padding: Int) = apply { paddingStart = padding }
@RequiresApi(Build.VERSION_CODES.M)
fun paddingEnd(padding: Int) = apply { paddingEnd = padding }
fun padding(padding: Int) = apply {
paddingLeft(padding).paddingTop(padding).paddingRight(padding).paddingBottom(padding)
}
@RequiresApi(Build.VERSION_CODES.M)
fun paddingRelative(padding: Int) = apply {
paddingStart(padding).paddingTop(padding).paddingEnd(padding).paddingBottom(padding)
}
fun add(drawable: Drawable) = apply { layers.add(Layer(drawable)) }
fun width(width: Int) = apply { layers.last().width = width }
fun height(height: Int) = apply { layers.last().height = height }
fun insetLeft(inset: Int) = apply { layers.last().insetLeft = inset }
fun insetTop(inset: Int) = apply { layers.last().insetTop = inset }
fun insetRight(inset: Int) = apply { layers.last().insetRight = inset }
fun insetBottom(inset: Int) = apply { layers.last().insetBottom = inset }
@RequiresApi(Build.VERSION_CODES.M)
fun insetStart(inset: Int) = apply { layers.last().insetStart = inset }
@RequiresApi(Build.VERSION_CODES.M)
fun insetEnd(inset: Int) = apply { layers.last().insetEnd = inset }
fun inset(inset: Int) =
apply { insetLeft(inset).insetTop(inset).insetRight(inset).insetBottom(inset) }
fun inset(insetLeft: Int, insetTop: Int, insetRight: Int, insetBottom: Int) = apply {
insetLeft(insetLeft).insetTop(insetTop).insetRight(insetRight).insetBottom(insetBottom)
}
@RequiresApi(Build.VERSION_CODES.M)
fun insetRelative(inset: Int) =
apply { insetStart(inset).insetTop(inset).insetEnd(inset).insetBottom(inset) }
@RequiresApi(Build.VERSION_CODES.M)
fun insetRelative(insetStart: Int, insetTop: Int, insetEnd: Int, insetBottom: Int) = apply {
insetStart(insetStart).insetTop(insetTop).insetEnd(insetEnd).insetBottom(insetBottom)
}
@RequiresApi(Build.VERSION_CODES.M)
fun gravity(gravity: Int) = apply { layers.last().gravity = gravity }
fun modify(
index: Int, drawable: Drawable,
width: Int = DIMEN_UNDEFINED,
height: Int = DIMEN_UNDEFINED,
insetLeft: Int = DIMEN_UNDEFINED,
insetTop: Int = DIMEN_UNDEFINED,
insetRight: Int = DIMEN_UNDEFINED,
insetBottom: Int = DIMEN_UNDEFINED,
insetStart: Int = DIMEN_UNDEFINED,
insetEnd: Int = DIMEN_UNDEFINED
) =
apply {
val layer = layers[index]
layer.drawable = drawable
if (width != DIMEN_UNDEFINED) layer.width = width
if (height != DIMEN_UNDEFINED) layer.height = height
if (insetLeft != DIMEN_UNDEFINED) layer.insetLeft = insetLeft
if (insetTop != DIMEN_UNDEFINED) layer.insetTop = insetTop
if (insetRight != DIMEN_UNDEFINED) layer.insetRight = insetRight
if (insetBottom != DIMEN_UNDEFINED) layer.insetBottom = insetBottom
if (insetStart != DIMEN_UNDEFINED) layer.insetStart = insetStart
if (insetEnd != DIMEN_UNDEFINED) layer.insetEnd = insetEnd
}
fun build(): LayerDrawable {
val layerDrawable = LayerDrawable(layers.map { it -> it.drawable }.toTypedArray())
for (i in 0 until layers.size) {
val layer = layers[i]
layerDrawable.setLayerInset(
i,
layer.insetLeft,
layer.insetTop,
layer.insetRight,
layer.insetBottom
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (layer.insetStart != DIMEN_UNDEFINED || layer.insetEnd != DIMEN_UNDEFINED) {
layerDrawable.setLayerInsetRelative(
i,
layer.insetStart,
layer.insetTop,
layer.insetEnd,
layer.insetBottom
)
}
}
layerDrawable.setId(i, i)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
layerDrawable.setLayerGravity(i, layer.gravity)
layerDrawable.setLayerInsetStart(i, layer.insetStart)
layerDrawable.setLayerInsetEnd(i, layer.insetEnd)
}
}
layerDrawable.paddingMode = paddingMode
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
layerDrawable.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom)
if (paddingStart != DIMEN_UNDEFINED || paddingEnd != DIMEN_UNDEFINED) {
layerDrawable.setPaddingRelative(
paddingStart,
paddingTop,
paddingEnd,
paddingBottom
)
}
}
return layerDrawable
}
internal class Layer(var drawable: Drawable) {
var gravity: Int = Gravity.NO_GRAVITY
var width: Int = -1
var height: Int = -1
var insetLeft: Int = 0
var insetTop: Int = 0
var insetRight: Int = 0
var insetBottom: Int = 0
var insetStart: Int = DIMEN_UNDEFINED
var insetEnd: Int = DIMEN_UNDEFINED
}
}

View File

@@ -1,61 +0,0 @@
/*
* TSBattery - A new way to save your battery avoid cancer apps hacker it.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/TSBattery
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
*/
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
import android.graphics.Path
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.PathShape
class PathShapeDrawableBuilder {
private var path: Path? = null
private var pathStandardWidth: Float = 100f
private var pathStandardHeight: Float = 100f
private var width: Int = -1
private var height: Int = -1
fun path(path: Path, pathStandardWidth: Float, pathStandardHeight: Float) = apply {
this.path = path
this.pathStandardWidth = pathStandardWidth
this.pathStandardHeight = pathStandardHeight
}
fun width(width: Int) = apply { this.width = width }
fun height(height: Int) = apply { this.height = height }
fun size(size: Int) = apply { width(size).height(size) }
fun build(custom: ((shapeDrawable: ShapeDrawable) -> Unit)? = null): ShapeDrawable {
val shapeDrawable = ShapeDrawable()
if (path == null || width <= 0 || height <= 0) {
return shapeDrawable
}
val pathShape = PathShape(path!!, pathStandardWidth, pathStandardHeight)
shapeDrawable.shape = pathShape
shapeDrawable.intrinsicWidth = width
shapeDrawable.intrinsicHeight = height
if (custom != null) {
custom(shapeDrawable)
}
return shapeDrawable
}
}

View File

@@ -1,73 +0,0 @@
/*
* TSBattery - A new way to save your battery avoid cancer apps hacker it.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/TSBattery
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
*/
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.drawable.*
import android.util.StateSet
class RippleDrawableBuilder : DrawableWrapperBuilder<RippleDrawableBuilder>() {
private var color: Int = Constants.DEFAULT_COLOR
private var colorStateList: ColorStateList? = null
private var radius: Int = -1
fun color(color: Int) = apply { this.color = color }
fun colorStateList(colorStateList: ColorStateList?) =
apply { this.colorStateList = colorStateList }
fun radius(radius: Int) = apply { this.radius = radius }
override fun build(): Drawable {
var drawable = this.drawable!!
val colorStateList = this.colorStateList ?: ColorStateList(
arrayOf(StateSet.WILD_CARD),
intArrayOf(color)
)
var mask = if (drawable is DrawableContainer) drawable.getCurrent() else drawable
if (mask is ShapeDrawable) {
val state = mask.getConstantState()
if (state != null) {
val temp = state.newDrawable().mutate() as ShapeDrawable
temp.paint.color = Color.BLACK
mask = temp
}
} else if (mask is GradientDrawable) {
val state = mask.getConstantState()
if (state != null) {
val temp = state.newDrawable().mutate() as GradientDrawable
temp.setColor(Color.BLACK)
mask = temp
}
} else {
mask = ColorDrawable(Color.BLACK)
}
val rippleDrawable = RippleDrawable(colorStateList, drawable, mask)
setRadius(rippleDrawable, radius)
rippleDrawable.invalidateSelf()
drawable = rippleDrawable
return drawable
}
}

View File

@@ -1,52 +0,0 @@
/*
* TSBattery - A new way to save your battery avoid cancer apps hacker it.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/TSBattery
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
*/
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
import android.graphics.drawable.Drawable
import android.graphics.drawable.RotateDrawable
class RotateDrawableBuilder : DrawableWrapperBuilder<RotateDrawableBuilder>() {
private var pivotX: Float = 0.5f
private var pivotY: Float = 0.5f
private var fromDegrees: Float = 0f
private var toDegrees: Float = 360f
fun pivotX(x: Float) = apply { pivotX = x }
fun pivotY(y: Float) = apply { pivotY = y }
fun fromDegrees(degree: Float) = apply { fromDegrees = degree }
fun toDegrees(degree: Float) = apply { toDegrees = degree }
override fun build(): Drawable {
val rotateDrawable = RotateDrawable()
drawable?.let {
setDrawable(rotateDrawable, it)
apply {
setPivotX(rotateDrawable, pivotX)
setPivotY(rotateDrawable, pivotY)
setFromDegrees(rotateDrawable, fromDegrees)
setToDegrees(rotateDrawable, toDegrees)
}
}
return rotateDrawable
}
}

View File

@@ -1,45 +0,0 @@
/*
* TSBattery - A new way to save your battery avoid cancer apps hacker it.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/TSBattery
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
*/
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
import android.graphics.drawable.Drawable
import android.graphics.drawable.ScaleDrawable
import android.view.Gravity
class ScaleDrawableBuilder : DrawableWrapperBuilder<ScaleDrawableBuilder>() {
private var level: Int = 10000
private var scaleGravity = Gravity.CENTER
private var scaleWidth: Float = 0f
private var scaleHeight: Float = 0f
fun level(level: Int) = apply { this.level = level }
fun scaleGravity(gravity: Int) = apply { this.scaleGravity = gravity }
fun scaleWidth(scale: Float) = apply { this.scaleWidth = scale }
fun scaleHeight(scale: Float) = apply { this.scaleHeight = scale }
override fun build(): Drawable {
val scaleDrawable = ScaleDrawable(drawable, scaleGravity, scaleWidth, scaleHeight)
scaleDrawable.level = level
return scaleDrawable
}
}

View File

@@ -1,60 +0,0 @@
/*
* TSBattery - A new way to save your battery avoid cancer apps hacker it.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/TSBattery
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
*/
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.StateListDrawable
import android.util.StateSet
class StateListDrawableBuilder {
private var pressed: Drawable? = null
private var disabled: Drawable? = null
private var selected: Drawable? = null
private var normal: Drawable = ColorDrawable(Color.TRANSPARENT)
fun pressed(pressed: Drawable?) = apply { this.pressed = pressed }
fun disabled(disabled: Drawable?) = apply { this.disabled = disabled }
fun selected(selected: Drawable?) = apply { this.selected = selected }
fun normal(normal: Drawable) = apply { this.normal = normal }
fun build(): StateListDrawable {
val stateListDrawable = StateListDrawable()
setupStateListDrawable(stateListDrawable)
return stateListDrawable
}
private fun setupStateListDrawable(stateListDrawable: StateListDrawable) {
pressed?.let {
stateListDrawable.addState(intArrayOf(android.R.attr.state_pressed), it)
}
disabled?.let {
stateListDrawable.addState(intArrayOf(-android.R.attr.state_enabled), it)
}
selected?.let {
stateListDrawable.addState(intArrayOf(android.R.attr.state_selected), it)
}
stateListDrawable.addState(StateSet.WILD_CARD, normal)
}
}

View File

@@ -19,41 +19,53 @@
*
* This file is Created by fankes on 2022/1/7.
*/
@file:Suppress("unused")
@file:Suppress("unused", "OPT_IN_USAGE", "EXPERIMENTAL_API_USAGE")
package com.fankes.tsbattery.utils.factory
import android.app.AlertDialog
import android.app.Dialog
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.GradientDrawable
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import com.fankes.tsbattery.R
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.highcapable.yukihookapi.annotation.CauseProblemsApi
import com.highcapable.yukihookapi.hook.factory.applyModuleTheme
/**
* 构造对话框
* @param isUseBlackTheme 是否使用深色主题
* @param it 对话框方法体
* @param initiate 对话框方法体
*/
fun Context.showDialog(isUseBlackTheme: Boolean = false, it: DialogBuilder.() -> Unit) =
DialogBuilder(this, isUseBlackTheme).apply(it).show()
inline fun Context.showDialog(initiate: DialogBuilder.() -> Unit) = DialogBuilder(context = this).apply(initiate).show()
/**
* 对话框构造器
* @param context 实例
* @param isUseBlackTheme 是否使用深色主题
*/
class DialogBuilder(private val context: Context, private val isUseBlackTheme: Boolean) {
class DialogBuilder(val context: Context) {
private var instance: AlertDialog.Builder? = null // 实例对象
/** 实例对象 */
private var instance: AlertDialog.Builder? = null
/** 对话框实例 */
private var dialogInstance: Dialog? = null
/** 自定义布局 */
private var customLayoutView: View? = null
init {
instance = AlertDialog.Builder(
context,
if (isUseBlackTheme) android.R.style.Theme_Material_Dialog else android.R.style.Theme_Material_Light_Dialog
)
instance = MaterialAlertDialogBuilder(context.applyModuleTheme(R.style.Theme_TSBattery))
}
/** 设置对话框不可关闭 */
fun noCancelable() = instance?.setCancelable(false)
fun noCancelable() {
instance?.setCancelable(false)
}
/** 设置对话框标题 */
var title
@@ -69,40 +81,61 @@ class DialogBuilder(private val context: Context, private val isUseBlackTheme: B
instance?.setMessage(value)
}
/** 设置进度条对话框消息内容 */
var progressContent
get() = ""
set(value) {
if (customLayoutView == null)
customLayoutView = LinearLayout(context).apply {
orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER or Gravity.START
addView(ProgressBar(context))
addView(View(context).apply { layoutParams = ViewGroup.LayoutParams(20.dp(context), 5) })
addView(TextView(context).apply {
tag = "progressContent"
text = value
})
setPadding(20.dp(context), 20.dp(context), 20.dp(context), 20.dp(context))
}
else customLayoutView?.findViewWithTag<TextView>("progressContent")?.text = value
}
/**
* 设置对话框确定按钮
* @param text 按钮文本内容
* @param it 点击事件
* @param callback 点击事件
*/
fun confirmButton(text: String = "确定", it: () -> Unit = {}) =
instance?.setPositiveButton(text) { _, _ -> it() }
fun confirmButton(text: String = "确定", callback: () -> Unit = {}) {
instance?.setPositiveButton(text) { _, _ -> callback() }
}
/**
* 设置对话框取消按钮
* @param text 按钮文本内容
* @param it 点击事件
* @param callback 点击事件
*/
fun cancelButton(text: String = "取消", it: () -> Unit = {}) =
instance?.setNegativeButton(text) { _, _ -> it() }
fun cancelButton(text: String = "取消", callback: () -> Unit = {}) {
instance?.setNegativeButton(text) { _, _ -> callback() }
}
/**
* 设置对话框第三个按钮
* @param text 按钮文本内容
* @param it 点击事件
* @param callback 点击事件
*/
fun neutralButton(text: String = "更多", it: () -> Unit = {}) =
instance?.setNeutralButton(text) { _, _ -> it() }
fun neutralButton(text: String = "更多", callback: () -> Unit = {}) {
instance?.setNeutralButton(text) { _, _ -> callback() }
}
/** 取消对话框 */
fun cancel() = dialogInstance?.cancel()
/** 显示对话框 */
internal fun show() = instance?.create()?.apply {
window?.setBackgroundDrawable(GradientDrawable(
GradientDrawable.Orientation.TOP_BOTTOM,
if (isUseBlackTheme) intArrayOf(0xFF2D2D2D.toInt(), 0xFF2D2D2D.toInt())
else intArrayOf(Color.WHITE, Color.WHITE)
).apply {
shape = GradientDrawable.RECTANGLE
gradientType = GradientDrawable.LINEAR_GRADIENT
cornerRadius = 15.dpFloat(this@DialogBuilder.context)
})
}?.show()
@CauseProblemsApi
fun show() = runInSafe {
instance?.create()?.apply {
customLayoutView?.let { setView(it) }
dialogInstance = this
}?.show()
}
}

View File

@@ -0,0 +1,82 @@
/*
* TSBattery - A new way to save your battery avoid cancer apps hacker it.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/TSBattery
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/3/13.
*/
@file:Suppress("unused")
package com.fankes.tsbattery.utils.factory
import com.highcapable.yukihookapi.hook.log.loggerE
/**
* 忽略异常返回值
* @param result 回调 - 如果异常为空
* @return [T] 发生异常时返回设定值否则返回正常值
*/
inline fun <T> safeOfNull(result: () -> T): T? = safeOf(default = null, result)
/**
* 忽略异常返回值
* @param result 回调 - 如果异常为 false
* @return [Boolean] 发生异常时返回设定值否则返回正常值
*/
inline fun safeOfFalse(result: () -> Boolean) = safeOf(default = false, result)
/**
* 忽略异常返回值
* @param result 回调 - 如果异常为 true
* @return [Boolean] 发生异常时返回设定值否则返回正常值
*/
inline fun safeOfTrue(result: () -> Boolean) = safeOf(default = true, result)
/**
* 忽略异常返回值
* @param result 回调 - 如果异常为 false
* @return [String] 发生异常时返回设定值否则返回正常值
*/
inline fun safeOfNothing(result: () -> String) = safeOf(default = "", result)
/**
* 忽略异常返回值
* @param result 回调 - 如果异常为 false
* @return [Int] 发生异常时返回设定值否则返回正常值
*/
inline fun safeOfNan(result: () -> Int) = safeOf(default = 0, result)
/**
* 忽略异常返回值
* @param default 异常返回值
* @param result 正常回调值
* @return [T] 发生异常时返回设定值否则返回正常值
*/
inline fun <T> safeOf(default: T, result: () -> T) = try {
result()
} catch (_: Throwable) {
default
}
/**
* 忽略异常运行
* @param msg 出错输出的消息 - 默认为空
* @param block 正常回调
*/
inline fun <T> T.runInSafe(msg: String = "", block: () -> Unit) {
runCatching(block).onFailure { if (msg.isNotBlank()) loggerE(msg = msg, e = it) }
}

View File

@@ -19,26 +19,36 @@
*
* This file is Created by fankes on 2022/1/7.
*/
@file:Suppress("DEPRECATION")
@file:Suppress("unused", "DiscouragedApi", "InternalInsetResource")
package com.fankes.tsbattery.utils.factory
import android.app.Activity
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.PackageInfoFlags
import android.content.res.Configuration
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.net.ConnectivityManager
import android.net.Uri
import android.os.Build
import android.provider.Settings
import android.widget.Toast
import com.fankes.tsbattery.application.TSApplication.Companion.appContext
import androidx.core.content.getSystemService
import androidx.core.content.pm.PackageInfoCompat
import com.fankes.tsbattery.BuildConfig
import com.google.android.material.snackbar.Snackbar
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication.Companion.appContext
/**
* 系统深色模式是否开启
* @return [Boolean] 是否开启
*/
val isSystemInDarkMode
get() = (appContext.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
val isSystemInDarkMode get() = appContext.isSystemInDarkMode
/**
* 系统深色模式是否没开启
@@ -47,37 +57,87 @@ val isSystemInDarkMode
inline val isNotSystemInDarkMode get() = !isSystemInDarkMode
/**
* 得到安装包信息
* @return [PackageInfo]
* 系统深色模式是否开启
* @return [Boolean] 是否开启
*/
val Context.packageInfo get() = packageManager?.getPackageInfo(packageName, 0) ?: PackageInfo()
val Context.isSystemInDarkMode get() = (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
/**
* 判断应用是否安装
* @return [Boolean]
* 系统深色模式是否没开启
* @return [Boolean] 是否开启
*/
val String.isInstall
get() = try {
appContext.packageManager.getPackageInfo(
this,
PackageManager.GET_UNINSTALLED_PACKAGES
)
true
} catch (e: Exception) {
false
}
inline val Context.isNotSystemInDarkMode get() = !isSystemInDarkMode
/**
* 得到版本信息
* @return [String]
* 得到 APP 安装包信息 (兼容)
* @param packageName APP 包名
* @param flag [PackageInfoFlags]
* @return [PackageInfo] or null
*/
val Context.versionName get() = packageInfo.versionName ?: ""
private fun Context.getPackageInfoCompat(packageName: String, flag: Number = 0) = runCatching {
@Suppress("DEPRECATION")
if (Build.VERSION.SDK_INT >= 33)
packageManager?.getPackageInfo(packageName, PackageInfoFlags.of(flag.toLong()))
else packageManager?.getPackageInfo(packageName, flag.toInt())
}.getOrNull()
/**
* 得到版本号
* 得到 APP 版本号 (兼容 [PackageInfo.getLongVersionCode])
* @return [Int]
*/
val Context.versionCode get() = packageInfo.versionCode
private val PackageInfo.versionCodeCompat get() = PackageInfoCompat.getLongVersionCode(this)
/**
* 判断 APP 是否安装
* @param packageName APP 包名
* @return [Boolean]
*/
fun Context.isInstall(packageName: String) = getPackageInfoCompat(packageName)?.let { true } ?: false
/**
* 得到 APP 版本信息
* @return [String]
*/
val Context.appVersionName get() = getPackageInfoCompat(packageName)?.versionName ?: ""
/**
* 得到 APP 版本号
* @return [Int]
*/
val Context.appVersionCode get() = getPackageInfoCompat(packageName)?.versionCodeCompat
/**
* 得到 APP 版本信息与版本号
* @param packageName APP 包名 - 默认为当前 APP
* @return [String]
*/
fun Context.appVersionBrandOf(packageName: String = getPackageName()) =
getPackageInfoCompat(packageName)?.let { "${it.versionName}(${it.versionCodeCompat})" } ?: ""
/**
* 得到 APP 名称
* @param packageName APP 包名 - 默认为当前 APP
* @return [String]
*/
fun Context.appNameOf(packageName: String = getPackageName()) =
getPackageInfoCompat(packageName)?.applicationInfo?.loadLabel(packageManager)?.toString() ?: ""
/**
* 得到 APP 图标
* @param packageName APP 包名 - 默认为当前 APP
* @return [Drawable] or null
*/
fun Context.appIconOf(packageName: String = getPackageName()) = getPackageInfoCompat(packageName)?.applicationInfo?.loadIcon(packageManager)
/**
* 网络连接是否正常
* @return [Boolean] 网络是否连接
*/
val Context.isNetWorkSuccess
get() = safeOfFalse {
@Suppress("DEPRECATION")
getSystemService<ConnectivityManager>()?.activeNetworkInfo != null
}
/**
* dp 转换为 pxInt
@@ -93,19 +153,91 @@ fun Number.dp(context: Context) = dpFloat(context).toInt()
*/
fun Number.dpFloat(context: Context) = toFloat() * context.resources.displayMetrics.density
/**
* 获取绝对状态栏高度
* @return [Int]
*/
val Context.absoluteStatusBarHeight
get() = safeOfNan {
resources.getDimensionPixelSize(resources.getIdentifier("status_bar_height", "dimen", "android"))
}
/**
* 弹出 [Toast]
* @param msg 提示内容
*/
fun toast(msg: String) = appContext.toast(msg)
/**
* 弹出 [Toast]
* @param msg 提示内容
*/
fun Context.toast(msg: String) = Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
/**
* 弹出 [Snackbar]
* @param msg 提示内容
* @param actionText 按钮文本 - 不写默认取消按钮
* @param callback 按钮事件回调
*/
fun Context.snake(msg: String, actionText: String = "", callback: () -> Unit = {}) =
Snackbar.make((this as Activity).findViewById(android.R.id.content), msg, Snackbar.LENGTH_LONG).apply {
if (actionText.isBlank()) return@apply
setActionTextColor(if (isSystemInDarkMode) Color.BLACK else Color.WHITE)
setAction(actionText) { callback() }
}.show()
/**
* 跳转 APP 自身设置界面
* @param packageName 包名
*/
fun Context.openSelfSetting(packageName: String) = runCatching {
if (packageName.isInstall)
fun Context.openSelfSetting(packageName: String = appContext.packageName) = runCatching {
if (isInstall(packageName))
startActivity(Intent().apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
data = Uri.fromParts("package", packageName, null)
})
else Toast.makeText(this, "你没有安装此应用", Toast.LENGTH_SHORT).show()
else snake(msg = "你没有安装此应用")
}.onFailure { toast(msg = "启动 $packageName 应用信息失败") }
/**
* 启动系统浏览器
* @param url 网址
* @param packageName 指定包名 - 可不填
*/
fun Context.openBrowser(url: String, packageName: String = "") = runCatching {
startActivity(Intent().apply {
if (packageName.isNotBlank()) setPackage(packageName)
action = Intent.ACTION_VIEW
data = Uri.parse(url)
/** 防止顶栈一样重叠在自己的 APP 中 */
flags = Intent.FLAG_ACTIVITY_NEW_TASK
})
}.onFailure {
Toast.makeText(this, "启动 $packageName 应用信息失败", Toast.LENGTH_SHORT).show()
if (packageName.isNotBlank()) snake(msg = "启动 $packageName 失败")
else snake(msg = "启动系统浏览器失败")
}
/**
* 隐藏或显示启动器图标
*
* - 你可能需要 LSPosed 的最新版本以开启高版本系统中隐藏 APP 桌面图标功能
* @param isShow 是否显示
*/
fun Context.hideOrShowLauncherIcon(isShow: Boolean) {
packageManager?.setComponentEnabledSetting(
ComponentName(packageName, "${BuildConfig.APPLICATION_ID}.Home"),
if (isShow) PackageManager.COMPONENT_ENABLED_STATE_DISABLED else PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP
)
}
/**
* 获取启动器图标状态
* @return [Boolean] 是否显示
*/
val Context.isLauncherIconShowing
get() = packageManager?.getComponentEnabledSetting(
ComponentName(packageName, "${BuildConfig.APPLICATION_ID}.Home")
) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED

View File

@@ -0,0 +1,145 @@
/*
* TSBattery - A new way to save your battery avoid cancer apps hacker it.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/TSBattery
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/3/20.
*/
@file:Suppress("NewApi")
package com.fankes.tsbattery.utils.tool
import android.app.Activity
import android.content.Context
import android.icu.text.SimpleDateFormat
import android.icu.util.Calendar
import android.icu.util.TimeZone
import com.fankes.tsbattery.utils.factory.*
import okhttp3.*
import org.json.JSONObject
import java.io.IOException
import java.io.Serializable
import java.util.*
/**
* 获取 Github Release 最新版本工具类
*/
object GithubReleaseTool {
/** 仓库作者 */
private const val REPO_AUTHOR = "fankes"
/** 仓库名称 */
private const val REPO_NAME = "TSBattery"
/**
* 获取最新版本信息
* @param context 实例
* @param version 当前版本
* @param result 成功后回调 - ([String] 最新版本,[Function] 更新对话框方法体)
*/
fun checkingForUpdate(context: Context, version: String, result: (String, () -> Unit) -> Unit) = checkingInternetConnect(context) {
OkHttpClient().newBuilder().build().newCall(
Request.Builder()
.url("https://api.github.com/repos/$REPO_AUTHOR/$REPO_NAME/releases/latest")
.get()
.build()
).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {}
override fun onResponse(call: Call, response: Response) = runInSafe {
JSONObject(response.body.string()).apply {
GithubReleaseBean(
name = getString("name"),
htmlUrl = getString("html_url"),
content = getString("body"),
date = getString("published_at").localTime()
).apply {
fun showUpdate() = context.showDialog {
title = "最新版本 $name"
msg = "发布于 $date\n\n" +
"更新日志\n\n" + content
confirmButton(text = "更新") { context.openBrowser(htmlUrl) }
cancelButton()
}
if (name != version) (context as? Activity?)?.runOnUiThread {
showUpdate()
result(name) { showUpdate() }
}
}
}
}
})
}
/**
* 检查网络连接情况
* @param context 实例
* @param callback 已连接回调
*/
private fun checkingInternetConnect(context: Context, callback: () -> Unit) = runInSafe {
if (context.isNetWorkSuccess)
OkHttpClient().newBuilder().build().newCall(
Request.Builder()
.url("https://www.baidu.com")
.get()
.build()
).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
(context as? Activity?)?.runOnUiThread {
context.showDialog {
title = "网络不可用"
msg = "应用的联网权限可能已被禁用,请开启联网权限以定期检查更新。"
confirmButton(text = "去开启") { context.openSelfSetting() }
cancelButton()
noCancelable()
}
}
}
override fun onResponse(call: Call, response: Response) = runInSafe {
(context as? Activity?)?.runOnUiThread { runInSafe { callback() } }
}
})
}
/**
* 格式化时间为本地时区
* @return [String] 本地时区时间
*/
private fun String.localTime() = replace("T", " ").replace("Z", "").let {
runCatching {
val local = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ROOT).apply { timeZone = Calendar.getInstance().timeZone }
val current = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ROOT).apply { timeZone = TimeZone.getTimeZone("GMT") }
local.format(current.parse(it))
}.getOrNull() ?: it
}
/**
* Github Release bean
* @param name 版本名称
* @param htmlUrl 网页地址
* @param content 更新日志
* @param date 发布时间
*/
private data class GithubReleaseBean(
var name: String,
var htmlUrl: String,
var content: String,
var date: String
) : Serializable
}

View File

@@ -0,0 +1,59 @@
/*
* TSBattery - A new way to save your battery avoid cancer apps hacker it.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/TSBattery
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/5/30.
*/
package com.fankes.tsbattery.utils.tool
import android.content.Context
import com.fankes.tsbattery.BuildConfig
import com.fankes.tsbattery.utils.factory.openBrowser
import com.fankes.tsbattery.utils.factory.showDialog
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.hook.factory.modulePrefs
import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
/**
* [YukiHookAPI] 的自动推广工具类
*/
object YukiPromoteTool {
/** 推广已读存储键值 */
private val YUKI_PROMOTE_READED = PrefsData("yuki_promote_readed_${BuildConfig.VERSION_NAME}", false)
/**
* 显示推广对话框
* @param context 实例
*/
fun promote(context: Context) {
fun saveReaded() = context.modulePrefs.put(YUKI_PROMOTE_READED, value = true)
if (context.modulePrefs.get(YUKI_PROMOTE_READED).not())
context.showDialog {
title = "面向开发者的推广"
msg = "你想快速拥有一个自己的 Xposed 模块吗,你只需要拥有基础的 Android 开发经验以及使用 Kotlin 编程语言即可。\n\n" +
"快来体验 YukiHookAPI这是一个使用 Kotlin 构建的高效 Hook API 与 Xposed 模块解决方案,助你的开发变得更轻松。"
confirmButton(text = "去看看") {
context.openBrowser(url = "https://github.com/fankes/YukiHookAPI")
saveReaded()
}
cancelButton(text = "我不是开发者") { saveReaded() }
noCancelable()
}
}
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FF7043" />
<corners android:radius="15dp" />
</shape>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="150.1dp"
android:height="150dp"
android:viewportWidth="1025"
android:viewportHeight="1024">
<path
android:fillColor="#ffffff"
android:pathData="M983.8,314.4c-25.7,-60.8 -62.5,-115.4 -109.4,-162.3 -46.9,-46.9 -101.5,-83.7 -162.3,-109.4 -62.9,-26.7 -129.9,-40.2 -198.8,-40.2S377.5,16 314.5,42.7C253.8,68.4 199.1,105.2 152.3,152.2c-46.9,46.9 -83.7,101.5 -109.4,162.3 -26.7,62.9 -40.2,129.9 -40.2,198.8s13.5,135.8 40.2,198.8c25.7,60.8 62.5,115.4 109.4,162.3 46.9,46.9 101.5,83.7 162.3,109.4 62.9,26.7 129.9,40.2 198.8,40.2s135.8,-13.5 198.8,-40.2c60.8,-25.7 115.4,-62.5 162.3,-109.4 46.9,-46.9 83.7,-101.5 109.4,-162.3 26.7,-62.9 40.2,-129.9 40.2,-198.8s-13.6,-135.9 -40.3,-198.9zM550.5,768.2c0,21 -17,38 -38,38s-38,-17 -38,-38L474.5,395.6c0,-21 17,-38 38,-38s38,17 38,38v372.6zM510.8,305.5c-29.2,0 -52.7,-23.7 -52.7,-52.7 0,-29.2 23.7,-52.7 52.7,-52.7 29.2,0 52.7,23.7 52.7,52.7 0.1,29.2 -23.6,52.7 -52.7,52.7z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="192dp"
android:height="150.39165dp"
android:viewportWidth="1307"
android:viewportHeight="1024">
<path
android:fillColor="#ffffff"
android:pathData="M268.7,566.5h929.6c36.3,0 72.6,-29 72.6,-72.6 0,-36.3 -29,-72.6 -72.6,-72.6H305l297.8,-297.8c29,-29 29,-72.6 0,-101.7 -29,-29 -72.6,-29 -101.7,0L72.6,450.3c-14.5,14.5 -21.8,36.3 -21.8,58.1 0,21.8 0,43.6 21.8,58.1l428.5,428.5c29,29 72.6,29 101.7,0 29,-29 29,-72.6 0,-101.7l-334.1,-326.8z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="150dp"
android:height="150dp"
android:viewportWidth="896"
android:viewportHeight="896">
<path
android:fillColor="#ffffff"
android:pathData="M448,0C200.58,0 0,200.58 0,448 0,695.42 200.58,896 448,896 695.42,896 896,695.42 896,448 896,200.58 695.42,0 448,0ZM663,612.05a36.11,36.11 0,0 1,0 50.95,36.11 36.11,0 0,1 -50.91,0L448,498.91 284,663a36.11,36.11 0,0 1,-51 0,36.11 36.11,0 0,1 0,-50.91L397,448.09 233,284a36.11,36.11 0,0 1,0 -51,36.11 36.11,0 0,1 51,0l164,164.09 164,-164a36.11,36.11 0,0 1,51 -0.09,36.11 36.11,0 0,1 0,51L498.91,448Z" />
</vector>

View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="#00000000"
android:pathData="M19,4H37L26,18H41L17,44L22,25H8L19,4Z"
android:strokeWidth="4"
android:strokeColor="#ffffff"
android:strokeLineJoin="round" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="150dp"
android:height="150dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#ffffff"
android:pathData="M512,12.6c-282.8,0 -512,229.2 -512,512 0,226.2 146.7,418.1 350.1,485.8 25.6,4.7 35,-11.1 35,-24.6 0,-12.2 -0.5,-52.5 -0.7,-95.3 -142.5,31 -172.5,-60.4 -172.5,-60.4 -23.3,-59.2 -56.8,-74.9 -56.8,-74.9 -46.5,-31.8 3.5,-31.1 3.5,-31.1 51.4,3.6 78.5,52.8 78.5,52.8 45.7,78.3 119.8,55.6 149,42.6 4.6,-33.1 17.9,-55.7 32.5,-68.5 -113.7,-12.9 -233.3,-56.9 -233.3,-253 0,-55.9 20,-101.6 52.8,-137.4 -5.3,-12.9 -22.8,-65 5,-135.5 0,0 43,-13.8 140.8,52.5 40.8,-11.4 84.6,-17 128.2,-17.2 43.5,0.2 87.3,5.9 128.3,17.2 97.7,-66.2 140.6,-52.5 140.6,-52.5 27.9,70.5 10.3,122.6 5,135.5 32.8,35.8 52.7,81.5 52.7,137.4 0,196.6 -119.8,239.9 -233.8,252.6 18.4,15.9 34.7,47 34.7,94.8 0,68.5 -0.6,123.6 -0.6,140.5 0,13.6 9.2,29.6 35.2,24.6 203.3,-67.8 349.9,-259.6 349.9,-485.8 0,-282.8 -229.2,-512 -512,-512z" />
</vector>

View File

@@ -0,0 +1,25 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="#00000000"
android:pathData="M9,18V42H39V18L24,6L9,18Z"
android:strokeWidth="4"
android:strokeColor="#ffffff"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:fillColor="#00000000"
android:pathData="M19,29V42H29V29H19Z"
android:strokeWidth="4"
android:strokeColor="#ffffff"
android:strokeLineJoin="round" />
<path
android:fillColor="#00000000"
android:pathData="M9,42H39"
android:strokeWidth="4"
android:strokeColor="#ffffff"
android:strokeLineCap="round" />
</vector>

View File

@@ -3,7 +3,7 @@
android:height="150dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FFffff"
android:pathData="M717.48,354.1a33.64,33.64 0,0 0,-17.41 -21.56,37.17 37.17,0 0,0 -28.42,-3.38c-65.54,17.77 -123.8,43.16 -174.69,76.19 -50.89,33.02 -93.9,70.76 -129.02,113.15 -35.12,42.5 -69.48,94.52 -102.96,156.11 -5.22,9.57 -6.66,18.69 -4.35,27.44a33.64,33.64 0,0 0,17.36 21.5,37.17 37.17,0 0,0 28.47,3.43 40.4,40.4 0,0 0,22.94 -17.36c7.99,-11.47 18.69,-28.16 32.1,-50.07 13.41,-21.91 23.04,-37.38 28.77,-46.39 40.29,-59.14 83.05,-104.96 128.15,-137.42 45.16,-32.46 102.4,-58.06 171.78,-76.85a37.58,37.58 0,0 0,22.99 -17.31,34 34,0 0,0 4.35,-27.44l-0.05,-0.05zM928.53,194c9.47,35.58 15.36,72.76 17.82,111.51 5.38,88.52 -9.37,169.47 -44.34,242.84 -35.02,73.37 -87.24,141.31 -156.67,203.93 -67.74,61.75 -142.64,103.73 -224.77,125.95a490.8,490.8 0,0 1,-164.25 16.13c-5.99,-0.36 -24.22,-3.89 -54.68,-10.5 -30.41,-6.66 -49.87,-8.81 -58.27,-6.55 -5.89,1.64 -11.52,9.57 -16.95,23.91 -5.43,14.34 -10.24,29.7 -14.34,46.08 -4.1,16.33 -10.24,32.05 -18.43,47.1 -8.19,15.1 -17.61,24.06 -28.26,26.93a60.47,60.47 0,0 1,-29.7 1.43,52.99 52.99,0 0,1 -20.58,-8.91 245.91,245.91 0,0 1,-26.06 -24.93,30.31 30.31,0 0,1 -7.53,-9.73 34.66,34.66 0,0 1,-2.87 -7.37c-3.48,-13.11 -1.43,-28.42 6.14,-45.93a209.92,209.92 0,0 1,27.6 -46.95c10.85,-13.82 20.58,-27.65 29.03,-41.63 8.5,-13.93 11.78,-24.47 9.93,-31.64 -0.41,-1.48 -4.86,-7.94 -13.36,-19.25a197.63,197.63 0,0 1,-15.36 -22.37,355.48 355.48,0 0,1 -20.48,-57.14 338.43,338.43 0,0 1,-8.86 -130.15,349.9 349.9,0 0,1 37.89,-121.5A504.68,504.68 0,0 1,204.3 351.74a531.35,531.35 0,0 1,97.95 -84.07c18.43,-12.24 43.72,-24.22 75.93,-35.94 32.1,-11.72 64.56,-22.32 97.38,-31.74 32.56,-9.42 65.02,-19.46 97.23,-30a455.37,455.37 0,0 0,86.32 -37.89c25.4,-14.64 43.42,-30.87 54.02,-48.64l11.78,-20.99 12.08,-20.17c7.99,-13.41 11.98,-18.48 11.83,-15.26 -0.1,3.28 5.79,-1.54 17.66,-14.44 11.93,-12.9 19.66,-15.87 23.24,-8.96 14.34,-3.89 29.49,1.23 45.62,15.36 16.13,14.08 30.41,32.72 42.8,55.86 12.44,23.19 23.04,45.21 31.74,66.15 8.7,20.94 14.9,38.55 18.64,52.74l0.05,0.26z"/>
</vector>
<path
android:fillColor="#FFffff"
android:pathData="M717.48,354.1a33.64,33.64 0,0 0,-17.41 -21.56,37.17 37.17,0 0,0 -28.42,-3.38c-65.54,17.77 -123.8,43.16 -174.69,76.19 -50.89,33.02 -93.9,70.76 -129.02,113.15 -35.12,42.5 -69.48,94.52 -102.96,156.11 -5.22,9.57 -6.66,18.69 -4.35,27.44a33.64,33.64 0,0 0,17.36 21.5,37.17 37.17,0 0,0 28.47,3.43 40.4,40.4 0,0 0,22.94 -17.36c7.99,-11.47 18.69,-28.16 32.1,-50.07 13.41,-21.91 23.04,-37.38 28.77,-46.39 40.29,-59.14 83.05,-104.96 128.15,-137.42 45.16,-32.46 102.4,-58.06 171.78,-76.85a37.58,37.58 0,0 0,22.99 -17.31,34 34,0 0,0 4.35,-27.44l-0.05,-0.05zM928.53,194c9.47,35.58 15.36,72.76 17.82,111.51 5.38,88.52 -9.37,169.47 -44.34,242.84 -35.02,73.37 -87.24,141.31 -156.67,203.93 -67.74,61.75 -142.64,103.73 -224.77,125.95a490.8,490.8 0,0 1,-164.25 16.13c-5.99,-0.36 -24.22,-3.89 -54.68,-10.5 -30.41,-6.66 -49.87,-8.81 -58.27,-6.55 -5.89,1.64 -11.52,9.57 -16.95,23.91 -5.43,14.34 -10.24,29.7 -14.34,46.08 -4.1,16.33 -10.24,32.05 -18.43,47.1 -8.19,15.1 -17.61,24.06 -28.26,26.93a60.47,60.47 0,0 1,-29.7 1.43,52.99 52.99,0 0,1 -20.58,-8.91 245.91,245.91 0,0 1,-26.06 -24.93,30.31 30.31,0 0,1 -7.53,-9.73 34.66,34.66 0,0 1,-2.87 -7.37c-3.48,-13.11 -1.43,-28.42 6.14,-45.93a209.92,209.92 0,0 1,27.6 -46.95c10.85,-13.82 20.58,-27.65 29.03,-41.63 8.5,-13.93 11.78,-24.47 9.93,-31.64 -0.41,-1.48 -4.86,-7.94 -13.36,-19.25a197.63,197.63 0,0 1,-15.36 -22.37,355.48 355.48,0 0,1 -20.48,-57.14 338.43,338.43 0,0 1,-8.86 -130.15,349.9 349.9,0 0,1 37.89,-121.5A504.68,504.68 0,0 1,204.3 351.74a531.35,531.35 0,0 1,97.95 -84.07c18.43,-12.24 43.72,-24.22 75.93,-35.94 32.1,-11.72 64.56,-22.32 97.38,-31.74 32.56,-9.42 65.02,-19.46 97.23,-30a455.37,455.37 0,0 0,86.32 -37.89c25.4,-14.64 43.42,-30.87 54.02,-48.64l11.78,-20.99 12.08,-20.17c7.99,-13.41 11.98,-18.48 11.83,-15.26 -0.1,3.28 5.79,-1.54 17.66,-14.44 11.93,-12.9 19.66,-15.87 23.24,-8.96 14.34,-3.89 29.49,1.23 45.62,15.36 16.13,14.08 30.41,32.72 42.8,55.86 12.44,23.19 23.04,45.21 31.74,66.15 8.7,20.94 14.9,38.55 18.64,52.74l0.05,0.26z" />
</vector>

View File

@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="#00000000"
android:pathData="M24,44C29.523,44 34.523,41.761 38.142,38.142C41.761,34.523 44,29.523 44,24C44,18.477 41.761,13.477 38.142,9.858C34.523,6.239 29.523,4 24,4C18.477,4 13.477,6.239 9.858,9.858C6.239,13.477 4,18.477 4,24C4,29.523 6.239,34.523 9.858,38.142C13.477,41.761 18.477,44 24,44Z"
android:strokeWidth="4"
android:strokeColor="#ffffff"
android:strokeLineJoin="round" />
<path
android:fillColor="#ffffff"
android:fillType="evenOdd"
android:pathData="M24,11C25.381,11 26.5,12.119 26.5,13.5C26.5,14.881 25.381,16 24,16C22.619,16 21.5,14.881 21.5,13.5C21.5,12.119 22.619,11 24,11Z" />
<path
android:fillColor="#00000000"
android:pathData="M24.5,34V20H23.5H22.5"
android:strokeWidth="4"
android:strokeColor="#ffffff"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:fillColor="#00000000"
android:pathData="M21,34H28"
android:strokeWidth="4"
android:strokeColor="#ffffff"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</vector>

View File

@@ -3,12 +3,13 @@
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.03609375"
android:scaleY="0.03609375"
android:translateX="35.52"
android:translateY="35.52">
<path
android:fillColor="#FFffff"
android:pathData="M717.48,354.1a33.64,33.64 0,0 0,-17.41 -21.56,37.17 37.17,0 0,0 -28.42,-3.38c-65.54,17.77 -123.8,43.16 -174.69,76.19 -50.89,33.02 -93.9,70.76 -129.02,113.15 -35.12,42.5 -69.48,94.52 -102.96,156.11 -5.22,9.57 -6.66,18.69 -4.35,27.44a33.64,33.64 0,0 0,17.36 21.5,37.17 37.17,0 0,0 28.47,3.43 40.4,40.4 0,0 0,22.94 -17.36c7.99,-11.47 18.69,-28.16 32.1,-50.07 13.41,-21.91 23.04,-37.38 28.77,-46.39 40.29,-59.14 83.05,-104.96 128.15,-137.42 45.16,-32.46 102.4,-58.06 171.78,-76.85a37.58,37.58 0,0 0,22.99 -17.31,34 34,0 0,0 4.35,-27.44l-0.05,-0.05zM928.53,194c9.47,35.58 15.36,72.76 17.82,111.51 5.38,88.52 -9.37,169.47 -44.34,242.84 -35.02,73.37 -87.24,141.31 -156.67,203.93 -67.74,61.75 -142.64,103.73 -224.77,125.95a490.8,490.8 0,0 1,-164.25 16.13c-5.99,-0.36 -24.22,-3.89 -54.68,-10.5 -30.41,-6.66 -49.87,-8.81 -58.27,-6.55 -5.89,1.64 -11.52,9.57 -16.95,23.91 -5.43,14.34 -10.24,29.7 -14.34,46.08 -4.1,16.33 -10.24,32.05 -18.43,47.1 -8.19,15.1 -17.61,24.06 -28.26,26.93a60.47,60.47 0,0 1,-29.7 1.43,52.99 52.99,0 0,1 -20.58,-8.91 245.91,245.91 0,0 1,-26.06 -24.93,30.31 30.31,0 0,1 -7.53,-9.73 34.66,34.66 0,0 1,-2.87 -7.37c-3.48,-13.11 -1.43,-28.42 6.14,-45.93a209.92,209.92 0,0 1,27.6 -46.95c10.85,-13.82 20.58,-27.65 29.03,-41.63 8.5,-13.93 11.78,-24.47 9.93,-31.64 -0.41,-1.48 -4.86,-7.94 -13.36,-19.25a197.63,197.63 0,0 1,-15.36 -22.37,355.48 355.48,0 0,1 -20.48,-57.14 338.43,338.43 0,0 1,-8.86 -130.15,349.9 349.9,0 0,1 37.89,-121.5A504.68,504.68 0,0 1,204.3 351.74a531.35,531.35 0,0 1,97.95 -84.07c18.43,-12.24 43.72,-24.22 75.93,-35.94 32.1,-11.72 64.56,-22.32 97.38,-31.74 32.56,-9.42 65.02,-19.46 97.23,-30a455.37,455.37 0,0 0,86.32 -37.89c25.4,-14.64 43.42,-30.87 54.02,-48.64l11.78,-20.99 12.08,-20.17c7.99,-13.41 11.98,-18.48 11.83,-15.26 -0.1,3.28 5.79,-1.54 17.66,-14.44 11.93,-12.9 19.66,-15.87 23.24,-8.96 14.34,-3.89 29.49,1.23 45.62,15.36 16.13,14.08 30.41,32.72 42.8,55.86 12.44,23.19 23.04,45.21 31.74,66.15 8.7,20.94 14.9,38.55 18.64,52.74l0.05,0.26z"/>
</group>
</vector>
<group
android:scaleX="0.03609375"
android:scaleY="0.03609375"
android:translateX="35.52"
android:translateY="35.52">
<path
android:fillColor="#FFffff"
android:pathData="M717.48,354.1a33.64,33.64 0,0 0,-17.41 -21.56,37.17 37.17,0 0,0 -28.42,-3.38c-65.54,17.77 -123.8,43.16 -174.69,76.19 -50.89,33.02 -93.9,70.76 -129.02,113.15 -35.12,42.5 -69.48,94.52 -102.96,156.11 -5.22,9.57 -6.66,18.69 -4.35,27.44a33.64,33.64 0,0 0,17.36 21.5,37.17 37.17,0 0,0 28.47,3.43 40.4,40.4 0,0 0,22.94 -17.36c7.99,-11.47 18.69,-28.16 32.1,-50.07 13.41,-21.91 23.04,-37.38 28.77,-46.39 40.29,-59.14 83.05,-104.96 128.15,-137.42 45.16,-32.46 102.4,-58.06 171.78,-76.85a37.58,37.58 0,0 0,22.99 -17.31,34 34,0 0,0 4.35,-27.44l-0.05,-0.05zM928.53,194c9.47,35.58 15.36,72.76 17.82,111.51 5.38,88.52 -9.37,169.47 -44.34,242.84 -35.02,73.37 -87.24,141.31 -156.67,203.93 -67.74,61.75 -142.64,103.73 -224.77,125.95a490.8,490.8 0,0 1,-164.25 16.13c-5.99,-0.36 -24.22,-3.89 -54.68,-10.5 -30.41,-6.66 -49.87,-8.81 -58.27,-6.55 -5.89,1.64 -11.52,9.57 -16.95,23.91 -5.43,14.34 -10.24,29.7 -14.34,46.08 -4.1,16.33 -10.24,32.05 -18.43,47.1 -8.19,15.1 -17.61,24.06 -28.26,26.93a60.47,60.47 0,0 1,-29.7 1.43,52.99 52.99,0 0,1 -20.58,-8.91 245.91,245.91 0,0 1,-26.06 -24.93,30.31 30.31,0 0,1 -7.53,-9.73 34.66,34.66 0,0 1,-2.87 -7.37c-3.48,-13.11 -1.43,-28.42 6.14,-45.93a209.92,209.92 0,0 1,27.6 -46.95c10.85,-13.82 20.58,-27.65 29.03,-41.63 8.5,-13.93 11.78,-24.47 9.93,-31.64 -0.41,-1.48 -4.86,-7.94 -13.36,-19.25a197.63,197.63 0,0 1,-15.36 -22.37,355.48 355.48,0 0,1 -20.48,-57.14 338.43,338.43 0,0 1,-8.86 -130.15,349.9 349.9,0 0,1 37.89,-121.5A504.68,504.68 0,0 1,204.3 351.74a531.35,531.35 0,0 1,97.95 -84.07c18.43,-12.24 43.72,-24.22 75.93,-35.94 32.1,-11.72 64.56,-22.32 97.38,-31.74 32.56,-9.42 65.02,-19.46 97.23,-30a455.37,455.37 0,0 0,86.32 -37.89c25.4,-14.64 43.42,-30.87 54.02,-48.64l11.78,-20.99 12.08,-20.17c7.99,-13.41 11.98,-18.48 11.83,-15.26 -0.1,3.28 5.79,-1.54 17.66,-14.44 11.93,-12.9 19.66,-15.87 23.24,-8.96 14.34,-3.89 29.49,1.23 45.62,15.36 16.13,14.08 30.41,32.72 42.8,55.86 12.44,23.19 23.04,45.21 31.74,66.15 8.7,20.94 14.9,38.55 18.64,52.74l0.05,0.26z" />
</group>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="150dp"
android:height="150dp"
android:viewportWidth="1008.7"
android:viewportHeight="1008.7">
<path
android:fillColor="#ffffff"
android:pathData="M504.4,0C226.3,0 0,226.3 0,504.4 0,782.5 226.3,1008.7 504.4,1008.7c278.1,0 504.4,-226.3 504.4,-504.4C1008.7,226.3 782.5,0 504.4,0ZM786.6,407.7 L458.6,743.9c-7.8,8 -18.6,12.6 -29.8,12.6h-0.2c-11.1,0 -21.8,-4.4 -29.7,-12.3L222.5,567.9c-16.4,-16.4 -16.4,-43 0,-59.4 16.4,-16.4 43,-16.4 59.4,0L428.2,654.8 726.5,348.9c16.3,-16.6 42.9,-16.9 59.4,-0.7 16.6,16.2 16.9,42.8 0.7,59.4z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="150dp"
android:height="150dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#ffffff"
android:pathData="m512,794a44.8,44.8 0,1 1,44.8 -44.8,44.8 44.8,0 0,1 -44.8,44.8zM471.9,230.8a40.1,40.1 0,0 1,80.2 0v369.1a40.1,40.1 0,0 1,-79.8 0zM512,0A512,512 0,1 0,1024 512,512 512,0 0,0 512,0Z" />
</vector>

View File

@@ -0,0 +1,403 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorThemeBackground"
android:orientation="vertical"
tools:context=".ui.activity.parasitic.ConfigActivity"
tools:ignore="HardcodedText,ContentDescription,UnusedAttribute,UselessParent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="0dp"
android:gravity="center|start"
android:paddingLeft="15dp"
android:paddingTop="15dp"
android:paddingRight="15dp"
android:paddingBottom="15dp">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/title_back_icon"
style="?android:attr/selectableItemBackgroundBorderless"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="20dp"
android:src="@drawable/ic_back"
android:tint="@color/colorTextGray"
android:tooltipText="返回" />
<TextView
android:id="@+id/title_name_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="2.5dp"
android:layout_weight="1"
android:singleLine="true"
android:text="@string/app_name"
android:textColor="@color/colorTextGray"
android:textSize="19sp"
android:textStyle="bold" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/title_module_icon"
android:layout_width="23dp"
android:layout_height="23dp"
android:layout_marginEnd="10dp"
android:padding="1.5dp"
android:src="@drawable/ic_icon"
android:tint="@color/colorTextGray"
android:tooltipText="打开模块主界面" />
</LinearLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fadingEdgeLength="10dp"
android:orientation="vertical"
android:requiresFadingEdge="vertical"
android:scrollbars="none">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="vertical"
android:paddingBottom="15dp">
<TextView
android:id="@+id/update_version_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_marginBottom="5dp"
android:background="@drawable/bg_orange_round"
android:gravity="center"
android:padding="5dp"
android:text="点击更新 %1"
android:textColor="@color/white"
android:textSize="13sp"
android:visibility="gone" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="5dp"
android:layout_marginRight="15dp"
android:background="@drawable/bg_permotion_round"
android:elevation="0dp"
android:gravity="center"
android:orientation="horizontal"
android:padding="15dp">
<ImageView
android:id="@+id/app_icon"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_marginEnd="15dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:gravity="center|start">
<TextView
android:id="@+id/app_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="7.5dp"
android:singleLine="true"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<TextView
android:id="@+id/app_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.85"
android:singleLine="true"
android:textColor="@color/colorTextGray"
android:textSize="12sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:alpha="0.85"
android:singleLine="true"
android:text="|"
android:textColor="@color/colorTextGray"
android:textSize="12sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.85"
android:singleLine="true"
android:text="模块版本:"
android:textColor="@color/colorTextGray"
android:textSize="12sp" />
<TextView
android:id="@+id/module_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.85"
android:singleLine="true"
android:textColor="@color/colorTextGray"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center|start"
android:orientation="horizontal">
<androidx.cardview.widget.CardView
android:layout_width="13dp"
android:layout_height="13dp"
android:layout_marginEnd="6dp"
app:cardBackgroundColor="@color/colorThemeBackground"
app:cardCornerRadius="50dp"
app:cardElevation="0dp">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/active_mode_icon"
android:layout_width="13dp"
android:layout_height="13dp"
android:src="@drawable/ic_success"
android:tint="#FF26A69A"
android:visibility="gone" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/inactive_mode_icon"
android:layout_width="13dp"
android:layout_height="13dp"
android:src="@drawable/ic_error"
android:tint="#FF7043"
android:visibility="gone" />
</androidx.cardview.widget.CardView>
<LinearLayout
android:id="@+id/unsupport_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
android:gravity="center|start"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.85"
android:singleLine="true"
android:text="未适配"
android:textColor="@color/colorTextGray"
android:textSize="12sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:alpha="0.85"
android:singleLine="true"
android:text="|"
android:textColor="@color/colorTextGray"
android:textSize="12sp" />
</LinearLayout>
<TextView
android:id="@+id/current_mode_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.85"
android:singleLine="true"
android:textColor="@color/colorTextGray"
android:textSize="12sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:alpha="0.85"
android:singleLine="true"
android:text="|"
android:textColor="@color/colorTextGray"
android:textSize="12sp" />
<TextView
android:id="@+id/executor_info_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:alpha="0.85"
android:singleLine="true"
android:textColor="@color/colorTextGray"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/info_tip_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:layout_marginRight="15dp"
android:alpha="0.8"
android:background="@drawable/bg_permotion_round"
android:lineSpacingExtra="10dp"
android:padding="15dp"
android:text="模块只对挂后台锁屏情况下有省电效果,请不要将过多的群提醒,消息通知打开,这样子在使用过程时照样会极其耗电。\n持续常驻使用{APP_NAME}依然会耗电,任何软件都是如此,模块是无法帮你做到前台不耗电的。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:layout_marginRight="15dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_permotion_round"
android:elevation="0dp"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingTop="10dp"
android:paddingRight="15dp"
android:paddingBottom="15dp">
<com.fankes.tsbattery.ui.view.MaterialSwitch
android:id="@+id/disable_all_hook_switch"
android:layout_width="match_parent"
android:layout_height="35dp"
android:layout_marginBottom="5dp"
android:text="停用省电策略"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="选择停用后模块将关闭所有省电功能,模块停止使用。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/item_qq_tim_config"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:layout_marginRight="15dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_permotion_round"
android:elevation="0dp"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingTop="10dp"
android:paddingRight="15dp">
<com.fankes.tsbattery.ui.view.MaterialSwitch
android:id="@+id/qq_tim_protect_mode_switch"
android:layout_width="match_parent"
android:layout_height="35dp"
android:layout_marginBottom="5dp"
android:text="启用保守模式"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<TextView
android:id="@+id/qq_tim_protect_tip_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="此选项默认关闭,默认情况下模块将会干掉{APP_NAME}自身的电源锁控制类,开启后模块将只对系统电源锁生效,如果你的{APP_NAME}视频通话等设置发生了故障,可以尝试开启这个功能。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:background="@drawable/bg_permotion_round"
android:elevation="0dp"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingTop="10dp"
android:paddingRight="15dp"
android:paddingBottom="15dp">
<com.fankes.tsbattery.ui.view.MaterialSwitch
android:id="@+id/qq_tim_core_service_switch"
android:layout_width="match_parent"
android:layout_height="35dp"
android:layout_marginBottom="5dp"
android:text="关闭 CoreService"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="关闭后可能会影响消息接收与视频通话,但是会达到省电效果,如果你的系统拥有推送服务 (HMS) 或 (MIPUSH) 可以尝试关闭。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<com.fankes.tsbattery.ui.view.MaterialSwitch
android:id="@+id/qq_tim_core_service_child_switch"
android:layout_width="match_parent"
android:layout_height="35dp"
android:layout_marginBottom="5dp"
android:text="关闭 CoreService$KernelService"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="这是一个辅助子服务,理论主服务关闭后子服务同样不会被启动,建议在保证消息接收的前提下可以尝试关闭子服务。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>

View File

@@ -6,8 +6,8 @@
android:layout_height="match_parent"
android:background="@color/colorThemeBackground"
android:orientation="vertical"
tools:context=".ui.MainActivity"
tools:ignore="HardcodedText,UseCompoundDrawables,ContentDescription,TooManyViews">
tools:context=".ui.activity.MainActivity"
tools:ignore="HardcodedText,UseCompoundDrawables,ContentDescription,UnusedAttribute">
<LinearLayout
android:layout_width="match_parent"
@@ -36,8 +36,9 @@
android:layout_height="27dp"
android:layout_marginEnd="5dp"
android:alpha="0.85"
android:src="@mipmap/ic_github"
android:tint="@color/colorTextGray" />
android:src="@drawable/ic_github"
android:tint="@color/colorTextGray"
android:tooltipText="项目地址" />
</LinearLayout>
<LinearLayout
@@ -58,7 +59,7 @@
android:layout_height="25dp"
android:layout_marginStart="25dp"
android:layout_marginEnd="5dp"
android:src="@mipmap/ic_warn"
android:src="@drawable/ic_warn"
android:tint="@color/white" />
<LinearLayout
@@ -79,164 +80,143 @@
android:textColor="@color/white"
android:textSize="18sp" />
<TextView
android:id="@+id/main_text_version"
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.8"
android:text="模块版本:%1"
android:gravity="center|start"
android:orientation="horizontal">
<TextView
android:id="@+id/main_text_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.8"
android:text="模块版本:%1"
android:textColor="@color/white"
android:textSize="13sp" />
<TextView
android:id="@+id/main_text_release_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:background="@drawable/bg_orange_round"
android:paddingLeft="5dp"
android:paddingTop="2dp"
android:paddingRight="5dp"
android:paddingBottom="2dp"
android:text="点击更新 %1"
android:textColor="@color/white"
android:textSize="11sp"
android:visibility="gone" />
</LinearLayout>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fadingEdgeLength="10dp"
android:fillViewport="true"
android:requiresFadingEdge="horizontal"
android:scrollbars="none">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center|start"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/main_qq_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:gravity="center|start"
android:orientation="horizontal">
<ImageView
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_marginEnd="5dp"
android:src="@mipmap/ic_qq_icon" />
<TextView
android:id="@+id/main_text_qq_ver"
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:id="@+id/main_tim_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:gravity="center|start"
android:orientation="horizontal">
<ImageView
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_marginEnd="5dp"
android:src="@mipmap/ic_tim_icon" />
<TextView
android:id="@+id/main_text_tim_ver"
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:id="@+id/main_wechat_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center|start"
android:orientation="horizontal">
<ImageView
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_marginEnd="5dp"
android:src="@mipmap/ic_wechat_icon" />
<TextView
android:id="@+id/main_text_wechat_ver"
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>
</HorizontalScrollView>
<TextView
android:id="@+id/main_text_api_way"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:alpha="0.6"
android:ellipsize="end"
android:singleLine="true"
android:text="%1"
android:textColor="@color/white"
android:textSize="13sp" />
<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/ic_qq_icon" />
<TextView
android:id="@+id/main_text_qq_noinstall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="3dp"
android:alpha="0.8"
android:background="@drawable/bg_red_round"
android:paddingLeft="2dp"
android:paddingTop="1dp"
android:paddingRight="2dp"
android:paddingBottom="1dp"
android:text="未安装"
android:textColor="@color/white"
android:textSize="9sp"
tools:ignore="SmallSp" />
<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/ic_tim_icon" />
<TextView
android:id="@+id/main_text_tim_noinstall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="3dp"
android:alpha="0.8"
android:background="@drawable/bg_red_round"
android:paddingLeft="2dp"
android:paddingTop="1dp"
android:paddingRight="2dp"
android:paddingBottom="1dp"
android:text="未安装"
android:textColor="@color/white"
android:textSize="9sp"
tools:ignore="SmallSp" />
<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:gravity="center|start"
android:orientation="horizontal">
<ImageView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="5dp"
android:src="@mipmap/ic_wechat_icon" />
<TextView
android:id="@+id/main_text_wechat_noinstall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="3dp"
android:alpha="0.8"
android:background="@drawable/bg_red_round"
android:paddingLeft="2dp"
android:paddingTop="1dp"
android:paddingRight="2dp"
android:paddingBottom="1dp"
android:text="未安装"
android:textColor="@color/white"
android:textSize="9sp"
tools:ignore="SmallSp" />
<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>
android:textSize="11sp"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
@@ -270,7 +250,7 @@
android:layout_height="15dp"
android:layout_marginEnd="15dp"
android:alpha="0.85"
android:src="@mipmap/ic_about"
android:src="@drawable/ic_about"
android:tint="@color/colorTextDark" />
<TextView
@@ -278,160 +258,12 @@
android:layout_height="wrap_content"
android:alpha="0.6"
android:lineSpacingExtra="5dp"
android:text="上述列出的版本号为最佳兼容版本,你可以点击进行查看。\n没有标注的版本在适配范围内的 APP 适用性都将有效,但可能不能达到最佳使用效果。\n如果当前版本失效请看下方的联系方式。"
android:text="你可以点击上述每个图标查看最佳兼容的 APP 版本。\n没有标注的版本在适配范围内的 APP 适用性都将有效,但可能不能达到最佳使用效果,建议保持使用适配内的版本。"
android:textColor="@color/colorTextGray"
android:textSize="10sp"
tools:ignore="SmallSp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="10dp"
android:layout_marginRight="15dp"
android:background="@drawable/bg_permotion_round"
android:elevation="0dp"
android:gravity="center"
android:orientation="vertical"
android:padding="15dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:gravity="center|start">
<ImageView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="5dp"
android:src="@mipmap/ic_qq_icon" />
<ImageView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="10dp"
android:src="@mipmap/ic_tim_icon" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alpha="0.85"
android:singleLine="true"
android:text="QQ、TIM"
android:textColor="@color/colorTextGray"
android:textSize="12sp" />
</LinearLayout>
<com.fankes.tsbattery.view.MaterialSwitch
android:id="@+id/qqtim_protect_mode_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="启用保守模式 [QQ]"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="此选项默认关闭,默认情况下模块将会干掉 QQ 自身的电源锁控制类,开启后模块将只对系统电源锁生效,如果你的 QQ 视频通话等设置发生了故障,可以尝试开启这个功能,开启后请重启 QQ。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<com.fankes.tsbattery.view.MaterialSwitch
android:id="@+id/shut_core_sv_qqtim_switch"
android:layout_width="match_parent"
android:layout_height="35dp"
android:layout_marginBottom="5dp"
android:text="关闭 CoreService"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="关闭后可能会影响消息接收与视频通话,但是会达到省电效果,如果你的系统拥有推送服务(HMS)或(GMS)可以尝试关闭。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<com.fankes.tsbattery.view.MaterialSwitch
android:id="@+id/shut_core_sv_kn_qqtim_switch"
android:layout_width="match_parent"
android:layout_height="35dp"
android:layout_marginBottom="5dp"
android:text="关闭 CoreService$KernelService"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="这是一个辅助子服务,理论主服务关闭后子服务同样不会被启动,建议在保证消息接收的前提下可以尝试关闭子服务。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="10dp"
android:layout_marginRight="15dp"
android:background="@drawable/bg_permotion_round"
android:elevation="0dp"
android:gravity="center"
android:orientation="vertical"
android:padding="15dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:gravity="center|start">
<ImageView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="10dp"
android:src="@mipmap/ic_wechat_icon" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alpha="0.85"
android:singleLine="true"
android:text="微信"
android:textColor="@color/colorTextGray"
android:textSize="12sp" />
</LinearLayout>
<com.fankes.tsbattery.view.MaterialSwitch
android:id="@+id/disable_wechat_sv_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="停用省电策略"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="选择停用后模块将不再对微信生效,可解决通话耳边不会黑屏和闪退问题,微信省电功能依然在开(画)发(饼),敬请期待。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -450,11 +282,21 @@
android:layout_marginBottom="10dp"
android:gravity="center|start">
<ImageView
<androidx.cardview.widget.CardView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="10dp"
android:src="@mipmap/ic_shot_icon" />
app:cardBackgroundColor="#FF00BCD4"
app:cardCornerRadius="50dp"
app:cardElevation="0dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:padding="2.5dp"
android:src="@drawable/ic_fast_op" />
</androidx.cardview.widget.CardView>
<TextView
android:layout_width="match_parent"
@@ -524,60 +366,41 @@
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingTop="15dp"
android:paddingRight="15dp">
<com.fankes.tsbattery.view.MaterialSwitch
android:id="@+id/notify_module_notify_tip_switch"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="通知栏显示守护状态"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
android:gravity="center|start">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="此功能仅支持 QQ、TIM在开启“系统通知栏显示 QQ、TIM 图标”后系统通知后方将在最后显示“TSBattery 守护中”字样以判断模块已经生效,若不喜欢,你可以随时关闭这个功能。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<androidx.cardview.widget.CardView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="10dp"
app:cardBackgroundColor="#FFFF9800"
app:cardCornerRadius="50dp"
app:cardElevation="0dp">
<com.fankes.tsbattery.view.MaterialSwitch
android:id="@+id/notify_module_info_switch"
android:layout_width="match_parent"
android:layout_height="35dp"
android:layout_marginBottom="5dp"
android:text="提示模块运行信息"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:padding="2.5dp"
android:src="@drawable/ic_home" />
</androidx.cardview.widget.CardView>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="模块工作正常情况下无需开启,如果你想测试模块是否正常激活,可以打开此提示,开启后将会在启动 QQ、TIM 或微信的时候提示运行信息。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alpha="0.85"
android:singleLine="true"
android:text="显示设置"
android:textColor="@color/colorTextGray"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:layout_marginRight="15dp"
android:background="@drawable/bg_permotion_round"
android:elevation="0dp"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingRight="15dp">
<com.fankes.tsbattery.view.MaterialSwitch
<com.fankes.tsbattery.ui.view.MaterialSwitch
android:id="@+id/hide_icon_in_launcher_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -591,9 +414,19 @@
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="隐藏模块图标后,模块不会再在桌面显示,你可以在 EdXposed、太极、LSPosed 中找到模块设置并打开,对原生系统无效。"
android:text="隐藏模块图标后界面可能会被关闭,将不会再在桌面显示,你可以在 EdXposed、LSPosed 中找到模块设置并打开。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="注意:请务必在 LSPosed 中关闭“强制显示桌面图标”功能"
android:textColor="#FF5722"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
@@ -606,28 +439,37 @@
android:elevation="0dp"
android:gravity="center"
android:orientation="vertical"
android:padding="15dp">
android:padding="15dp"
android:paddingTop="15dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginBottom="15dp"
android:gravity="center|start">
<androidx.constraintlayout.utils.widget.ImageFilterView
<androidx.cardview.widget.CardView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="5dp"
android:alpha="0.85"
android:src="@mipmap/ic_about"
android:tint="@color/colorTextGray" />
android:layout_marginEnd="10dp"
app:cardBackgroundColor="#FF03A9F4"
app:cardCornerRadius="50dp"
app:cardElevation="0dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:padding="2dp"
android:src="@drawable/ic_info" />
</androidx.cardview.widget.CardView>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alpha="0.85"
android:singleLine="true"
android:text="使用帮助说明"
android:text="使用帮助&amp;说明"
android:textColor="@color/colorTextGray"
android:textSize="12sp" />
</LinearLayout>
@@ -637,8 +479,8 @@
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:text="Q.这个模块是做什么的?\nA.此模块的诞生来源于国内厂商毒瘤 APP 强行霸占后台耗电QQ 在 8.6.0 版本以后也只是接入了 HMS 推送,但是可笑的是开发组却并没有删除之前疯狂耗电的接收消息方法,于是这个模块就诞生了。"
android:lineSpacingExtra="10dp"
android:text="Q.这个模块是做什么的?\nA.此模块的诞生来源于国内厂商毒瘤 APP 强行霸占后台耗电QQ 在 8.6.0 版本以后也只是接入了 HMS 推送,但是可笑的是开发组却并没有删除之前疯狂耗电的接收消息方法,于是这个模块就因此而诞生了。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
@@ -647,7 +489,7 @@
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:lineSpacingExtra="10dp"
android:text="Q.原理是什么?\nA.模块有两套工作方式,一种是针对 QQ、TIM Hook 掉系统自身的电源锁“WakeLock”使其不能影响系统休眠这样子在锁屏的时候 QQ、TIM 就可以进入睡眠状态。第二种就是针对 QQ、TIM 删除其自身的无用耗电疯狂循环检测后台强行保活服务。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
@@ -657,8 +499,8 @@
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:text="Q.如何使用?\nA.目前模块支持 LSPosed、EdXposed 以及太极(无极)框架,在太极和 LSPosed 的作用域中,只需勾选 QQ、TIM、微信即可模块可以做到即插即用激活后无需重启手机重启 QQ、TIM 或微信就可以了。"
android:lineSpacingExtra="10dp"
android:text="Q.如何使用?\nA.目前模块支持 LSPosed、LSPatch 以及太极和一些常见的免 Root 框架,在太极和 LSPosed 的作用域中,只需勾选 QQ、TIM、微信即可模块可以做到即插即用激活后无需重启手机重启 QQ、TIM 或微信就可以了。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
@@ -667,7 +509,17 @@
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:lineSpacingExtra="10dp"
android:text="Q.配置界面在哪里?\nA.从 4.0 版本开始模块已将配置界面转移到目标 APP 中,你可以从以下途径找到模块配置界面:\nQQ: 设置 > TSBattery\nTIM: 设置 > TSBattery\n微信: 设置 > 右上角 TSBattery 图标\n你还可以从此界面上方的“快捷操作”中快速进入指定配置界面。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.8"
android:lineSpacingExtra="10dp"
android:text="Q.激活后一定可以非常省电吗?\nA.并不,模块只能减少 QQ、TIM、微信的耗电但是请务必记住这一点省电只是一个理论上的东西实际水平由你使用的系统和硬件决定如果你在前台疯狂使用 QQ、TIM那么照样会耗电模块只能保证后台运行和锁屏时毒瘤不会消耗过多的无用的电量仅此而已。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
@@ -677,7 +529,7 @@
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:lineSpacingExtra="10dp"
android:text="Q.模块是否需要挂后台?\nA.模块完全不需要挂后台,模块只是一个控制和显示的工具,真正的任务交由 Hook 处理,若出现失效的情况请发送模块运行日志给我们而不是将模块挂后台。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
@@ -687,7 +539,7 @@
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:lineSpacingExtra="10dp"
android:text="Q.关于目前微信的适配情况?\nA.微信适配尚在实验阶段,敬请期待。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
@@ -695,18 +547,8 @@
<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.如何对单独的 APP 生效模块?\nA.请在 LSPosed 中勾选对应定义域即可,我们不建议使用 EdXposed。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:lineSpacingExtra="10dp"
android:text="Q.如何反馈问题?\nA.酷安关注 @星夜不荟"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
@@ -762,19 +604,34 @@
android:textSize="16sp" />
</LinearLayout>
<TextView
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_marginBottom="10dp"
android:autoLink="web"
android:background="@drawable/bg_permotion_round"
android:lineSpacingExtra="6dp"
android:padding="10dp"
android:text="此模块使用 YukiHookAPI 构建。\n点击这里了解更多 https://github.com/fankes/YukiHookAPI"
android:textColor="@color/colorTextGray"
android:textSize="12sp" />
android:gravity="center|start"
android:orientation="horizontal"
android:padding="10dp">
<ImageView
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginEnd="10dp"
android:src="@mipmap/ic_yukihookapi" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autoLink="web"
android:ellipsize="end"
android:lineSpacingExtra="6dp"
android:maxLines="2"
android:text="此模块使用 YukiHookAPI 构建。\n了解更多 https://github.com/fankes/YukiHookAPI"
android:textColor="@color/colorTextGray"
android:textSize="11sp" />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -1,16 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<resources>
<!-- Base application theme. -->
<style name="Theme.TSBattery" parent="Theme.Material3.DayNight">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorPrimary">@color/colorPrimaryAccent</item>
<item name="colorPrimaryVariant">@color/colorPrimaryAccent</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<item name="colorSecondary">@color/colorPrimaryAccent</item>
<item name="colorSecondaryVariant">@color/colorPrimaryAccent</item>
<item name="colorOnSecondary">@color/colorPrimaryAccent</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="module_scope">
<item>com.tencent.mobileqq</item>
<item>com.tencent.tim</item>
<item>com.tencent.mm</item>
</string-array>
</resources>

View File

@@ -1,10 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#656565</color>
<color name="purple_500">#656565</color>
<color name="purple_700">#656565</color>
<color name="teal_200">#656565</color>
<color name="teal_700">#656565</color>
<color name="colorPrimaryAccent">#656565</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@@ -1,16 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<resources>
<!-- Base application theme. -->
<style name="Theme.TSBattery" parent="Theme.Material3.DayNight">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/teal_700</item>
<item name="colorPrimary">@color/colorPrimaryAccent</item>
<item name="colorPrimaryVariant">@color/colorPrimaryAccent</item>
<item name="colorOnPrimary">@color/colorPrimaryAccent</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<item name="colorSecondary">@color/colorPrimaryAccent</item>
<item name="colorSecondaryVariant">@color/colorPrimaryAccent</item>
<item name="colorOnSecondary">@color/colorPrimaryAccent</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -1,12 +1,13 @@
plugins {
id 'com.android.application' version '7.1.2' apply false
id 'com.android.library' version '7.1.2' apply false
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
id 'com.android.application' version '7.3.1' apply false
id 'com.android.library' version '7.3.1' apply false
id 'org.jetbrains.kotlin.android' version '1.7.22' apply false
}
ext {
appVersionName = "3.3"
appVersionCode = 12
appVersionName = "4.2"
appVersionCode = 27
enableR8 = true
}
task clean(type: Delete) {

View File

@@ -6,7 +6,7 @@
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
org.gradle.jvmargs=-XX:+UseParallelGC
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
@@ -18,4 +18,6 @@ android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
kotlin.code.style=official
# Incremental
kotlin.incremental.useClasspathSnapshot=true

View File

@@ -1,6 +1,6 @@
#Mon Feb 14 23:27:58 CST 2022
#Wed May 25 04:34:58 CST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

View File

@@ -10,6 +10,7 @@ dependencyResolutionManagement {
repositories {
google()
maven { url "https://api.xposed.info/" }
maven { url "https://www.jitpack.io" }
maven { url "https://s01.oss.sonatype.org/content/repositories/releases" }
mavenCentral()
}