131 Commits
normal ... 3.5

Author SHA1 Message Date
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
48eb942167 Update version to 3.3,support QQ 8.8.83 2022-03-18 15:10:57 +08:00
fe11d0e67b 取消缓存设置实时生效 2022-03-18 15:06:48 +08:00
5fffb0b154 增加通知栏守护状态可关闭功能 2022-03-18 15:05:40 +08:00
316db6f887 Update YukiHookAPI 2022-03-18 14:51:20 +08:00
17b3d8ac9a Merge code 2022-03-18 14:51:00 +08:00
6d58f0330b Merge code 2022-03-18 14:25:35 +08:00
1c42bc4def 适配 QQ 8.8.83 2022-03-18 14:19:59 +08:00
a3772e3673 Update YukiHookAPI 2022-03-18 13:59:45 +08:00
af20fad070 Update YukiHookAPI 2022-03-18 05:59:29 +08:00
e510e5d043 Update README.md 2022-03-17 23:56:55 +08:00
2ebc0bc2dc Update README.md 2022-03-17 23:56:08 +08:00
3e23c67ad2 Merge code 2022-03-17 05:39:20 +08:00
6ff9a08366 调整了一个按钮样式 2022-03-17 04:10:52 +08:00
8a90303228 Merge code 2022-03-13 01:00:05 +08:00
5b411227d9 Update YukiHookAPI 2022-03-06 01:02:11 +08:00
88db36a848 修正文案 2022-03-05 00:24:09 +08:00
9b3585c309 Update version to 3.2,support QQ 8.8.80 2022-02-25 22:24:38 +08:00
8c38183869 适配 QQ 8.8.80 2022-02-25 22:22:18 +08:00
a1bc86cb81 适配 QQ 8.8.8.0 2022-02-25 22:04:57 +08:00
3fb12ae743 Merge code 2022-02-25 21:36:44 +08:00
f23034639e Merge code 2022-02-20 03:42:59 +08:00
ccff6c04fd Merge code 2022-02-19 03:11:32 +08:00
48fbadb692 更新依赖库版本 2022-02-18 04:17:35 +08:00
0594352859 规范资源文件命名 2022-02-16 03:35:05 +08:00
9aceec38b6 修改文案 2022-02-15 22:16:22 +08:00
b925296870 Merge new version 2022-02-15 13:40:56 +08:00
28b03d2205 Merge new version 2022-02-15 13:39:41 +08:00
aeb6654550 Merge new version 2022-02-15 13:39:12 +08:00
82ff6ac311 Merge new version 2022-02-15 12:51:13 +08:00
74ba88f201 优化代码 2022-02-15 03:32:22 +08:00
3144a160d4 Update README.md 2022-02-15 02:00:47 +08:00
34bfb4660e Update README.md 2022-02-15 02:00:24 +08:00
8d10a17914 Update README.md 2022-02-15 01:54:32 +08:00
1edd92ca79 Refactor to YukiHookAPI https://github.com/fankes/YukiHookAPI 2022-02-15 01:47:07 +08:00
4b583df6ae 更新文案 2022-02-10 19:53:11 +08:00
f6d51a7633 更新文案 2022-02-10 19:29:42 +08:00
a1791f327b 更新文案 2022-02-10 19:24:49 +08:00
3d78411738 更新文案 2022-02-10 19:24:12 +08:00
72597510f2 更新文案 2022-02-10 19:21:35 +08:00
5b371ba3da 更新文案 2022-02-10 19:20:37 +08:00
751b0c4ce5 更新文案 2022-02-10 18:57:08 +08:00
73677d1c33 更新文案 2022-02-10 18:54:57 +08:00
ed658c83e6 更新版本协议到 A-GPL3.0 2022-02-10 18:53:30 +08:00
Fankesyooni
0b4c58293a Update README.md 2022-02-10 18:40:47 +08:00
Fankesyooni
5a51ef571a Update README.md 2022-02-10 18:38:29 +08:00
Fankesyooni
4bd738b206 Update LICENSE 2022-02-10 18:38:07 +08:00
4a0473892b 修正文案 2022-02-07 21:15:48 +08:00
14354ec06f 摆烂 2022-02-07 00:39:32 +08:00
7eca11ed18 整理规范代码 2022-02-03 20:29:07 +08:00
4c13768500 整理规范代码 2022-01-30 23:07:14 +08:00
e30afc4011 适配夜间模式以及 Material3 2022-01-30 19:21:21 +08:00
7a1c53ed17 Update Version to 3.1 Support QQ 8.8.68 2022-01-25 05:51:16 +08:00
a4d62137a2 优化代码,适配 Play 版本 2022-01-25 05:47:14 +08:00
beb8da210d 优化代码,适配 Play 版本 2022-01-25 05:45:23 +08:00
60aecc67bf 优化文案 2022-01-25 05:23:46 +08:00
69faade0e4 优化代码 2022-01-25 05:22:13 +08:00
4ecc694dd4 优化代码 2022-01-25 05:14:44 +08:00
674f0a9cd7 优化代码 2022-01-25 04:59:39 +08:00
b14b9e02e2 优化代码 2022-01-25 04:58:19 +08:00
bacff6b275 加入快捷跳转应用信息页面操作 2022-01-25 04:36:38 +08:00
5eb2729eb6 加入多项 Hook 策略 2022-01-25 04:22:58 +08:00
d93b4bdc7f 加入多项 Hook 策略 2022-01-25 04:17:37 +08:00
f1e64c293c 加入多项 Hook 策略 2022-01-25 04:10:34 +08:00
8b2663c3cb 加入多项 Hook 策略 2022-01-25 04:06:27 +08:00
a5b1a57a93 加入多项 Hook 策略 2022-01-25 03:46:44 +08:00
d2dd6c9f88 Update README.md 2022-01-24 11:30:02 +08:00
e4d1ea8519 优化代码 2022-01-24 06:13:21 +08:00
d293e4656d 优化代码 2022-01-24 06:05:35 +08:00
731cfc13d4 优化代码 2022-01-24 05:54:13 +08:00
342128a692 优化代码 2022-01-24 05:52:27 +08:00
c08facd9f6 优化代码 2022-01-24 05:49:57 +08:00
3dfd69dbc7 优化代码 2022-01-24 05:48:35 +08:00
d13aee534e 优化代码注释 2022-01-24 05:42:49 +08:00
9f8f21dc9a 优化注释 2022-01-24 05:42:40 +08:00
90258a07c1 日常维护 2022-01-24 05:06:37 +08:00
c6bed6549e Update Version to 3.0 The brand new theme Material You. 2022-01-09 21:33:17 +08:00
b26b78e268 Update Version to 3.0 The brand new theme Material You. 2022-01-08 01:32:02 +08:00
ef97306151 Update Version to 3.0 The brand new theme Material You. 2022-01-08 01:29:44 +08:00
26a4a11149 Update Version to 3.0 The brand new theme Material You. 2022-01-08 01:29:23 +08:00
dc14585805 合并代码,加入全新 Material You 风格主题 2022-01-08 01:26:50 +08:00
8841ad7f59 合并代码,加入全新 Material You 风格主题 2022-01-08 01:12:20 +08:00
190f59451f 重构界面,加入安装状态图标 2022-01-08 00:11:51 +08:00
7ce09ac4ef 合并优化代码,封装对话框代码 2022-01-07 23:48:49 +08:00
a182e0e4d5 合并优化代码,封装对话框代码 2022-01-07 23:47:49 +08:00
bb69b762d0 Support QQ 8.8.55 2022-01-07 23:16:02 +08:00
e276ee4172 Update Banner Logo 2021-12-23 08:19:20 +08:00
692acd3358 Update Banner Logo 2021-12-23 08:17:43 +08:00
6360859255 Update Banner Logo 2021-12-23 08:15:21 +08:00
22f8e14afb Update Banner Logo 2021-12-23 08:14:28 +08:00
07a6fb6cf7 Update Banner Logo 2021-12-23 08:10:08 +08:00
163eecc0b8 Update Version to 2.5 Support WeChat(Base Save Battery) 2021-12-22 01:12:54 +08:00
5707a8b394 微信省电(新建文件夹) 2021-12-22 01:07:16 +08:00
11aa064b90 微信省电(新建文件夹) 2021-12-22 00:36:02 +08:00
15b1bfa498 合并优化代码,优化模块主界面显示效果,加入微信省电的未来计划(新建文件夹) 2021-12-22 00:28:23 +08:00
Fankesyooni
673dc8127b Merge pull request #6 from StarWishsama/master
支持 QQ 8.8.35
2021-12-21 23:44:51 +08:00
StarWishsama
7d9c360c9d feat: support QQ 8.8.35 2021-12-21 22:58:50 +08:00
d5e885db6c 更新了一个日志外显功能,并将 API 降回 26 2021-12-07 00:30:31 +08:00
7481bbd7ef Update README 2021-11-28 13:29:45 +08:00
23c94e4e26 Support QQ 8.8.50 Update version to 2.4 2021-11-27 19:18:24 +08:00
ead73eb23d Support QQ 8.8.50 Update version to 2.4 2021-11-27 19:16:50 +08:00
5d82e9a104 整理代码并更新依赖版本 2021-11-27 18:59:44 +08:00
Fankesyooni
14d1b9c5ac Merge pull request #4 from JiZhi-Error/master
Support QQ 8.8.50
2021-11-24 18:52:10 +08:00
JiZhi
44ea28edda Change the key file path to a relative path
Support QQ 8.8.50
2021-11-20 22:38:12 +08:00
b075fd8424 Update to 2.3 support QQ 8.8.38 2021-11-09 19:55:52 +08:00
4395f31895 Update to 2.3 support QQ 8.8.38 2021-11-09 19:37:22 +08:00
a25d2b814b Update to 2.3 support QQ 8.8.38 2021-11-09 19:37:01 +08:00
74 changed files with 4437 additions and 1353 deletions

4
.idea/gradle.xml generated
View File

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

View File

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

17
.idea/misc.xml generated
View File

@@ -1,6 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="app/src/main/res/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/mipmap-anydpi-v26/ic_launcher.xml" value="0.2495" />
</map>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

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

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

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

47
CONTRIBUTING.md Normal file
View File

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

632
LICENSE
View File

@@ -1,107 +1,100 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
# GNU AFFERO GENERAL PUBLIC LICENSE
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Version 3, 19 November 2007
Preamble
Copyright (C) 2007 Free Software Foundation, Inc.
<https://fsf.org/>
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
The licenses for most software and other practical works are designed
### Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains
free software for all its users.
When we speak of free software, we are referring to freedom, not
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing
under this license.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
### TERMS AND CONDITIONS
0. Definitions.
#### 0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"This License" refers to version 3 of the GNU Affero General Public
License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"Copyright" also means copyright-like laws that apply to other kinds
of works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of
an exact copy. The resulting work is called a "modified version" of
the earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user
through a computer network, with no transfer of a copy, is not
conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
An interactive user interface displays "Appropriate Legal Notices" to
the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
@@ -109,18 +102,18 @@ work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
#### 1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
The "source code" for a work means the preferred form of the work for
making modifications to it. "Object code" means any non-source form of
a work.
A "Standard Interface" means an interface that either is an official
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
@@ -131,7 +124,7 @@ implementation is available to the public in source code form. A
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
@@ -144,16 +137,15 @@ linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source need not include anything that users can
regenerate automatically from other parts of the Corresponding Source.
The Corresponding Source for a work in source code form is that
same work.
The Corresponding Source for a work in source code form is that same
work.
2. Basic Permissions.
#### 2. Basic Permissions.
All rights granted under this License are granted for the term of
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
@@ -161,40 +153,40 @@ covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
You may make, run and propagate covered works that you do not convey,
without conditions so long as your license otherwise remains in force.
You may convey covered works to others for the sole purpose of having
them make modifications exclusively for you, or provide you with
facilities for running those works, provided that you comply with the
terms of this License in conveying all material for which you do not
control copyright. Those thus making or running the covered works for
you must do so exclusively on your behalf, under your direction and
control, on terms that prohibit them from making any copies of your
copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
Conveying under any other circumstances is permitted solely under the
conditions stated below. Sublicensing is not allowed; section 10 makes
it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such
circumvention is effected by exercising rights under this License with
respect to the covered work, and you disclaim any intention to limit
operation or modification of the work as a means of enforcing, against
the work's users, your or third parties' legal rights to forbid
circumvention of technological measures.
4. Conveying Verbatim Copies.
#### 4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
@@ -202,37 +194,35 @@ non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
#### 5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
terms of section 4, provided that you also meet all of these
conditions:
a) The work must carry prominent notices stating that you modified
- a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
- b) The work must carry prominent notices stating that it is
released under this License and any conditions added under
section 7. This requirement modifies the requirement in section 4
to "keep intact all notices".
- c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
- d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
@@ -242,19 +232,18 @@ beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
#### 6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
You may convey a covered work in object code form under the terms of
sections 4 and 5, provided that you also convey the machine-readable
Corresponding Source under the terms of this License, in one of these
ways:
a) Convey the object code in, or embodied in, a physical product
- a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
- b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
@@ -263,16 +252,14 @@ in one of these ways:
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
conveying of source, or (2) access to copy the Corresponding
Source from a network server at no charge.
- c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
- d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
@@ -284,38 +271,38 @@ in one of these ways:
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
- e) Convey the object code using peer-to-peer transmission,
provided you inform other peers where the object code and
Corresponding Source of the work are being offered to the general
public at no charge under subsection 6d.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal,
family, or household purposes, or (2) anything designed or sold for
incorporation into a dwelling. In determining whether a product is a
consumer product, doubtful cases shall be resolved in favor of
coverage. For a particular product received by a particular user,
"normally used" refers to a typical or common use of that class of
product, regardless of the status of the particular user or of the way
in which the particular user actually uses, or expects or is expected
to use, the product. A product is a consumer product regardless of
whether the product has substantial commercial, industrial or
non-consumer uses, unless such uses represent the only significant
mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to
install and execute modified versions of a covered work in that User
Product from a modified version of its Corresponding Source. The
information must suffice to ensure that the continued functioning of
the modified object code is in no case prevented or interfered with
solely because modification has been made.
If you convey an object code work under this section in, or with, or
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
@@ -326,23 +313,24 @@ if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or
updates for a work that has been modified or installed by the
recipient, or for the User Product in which it has been modified or
installed. Access to a network may be denied when the modification
itself materially and adversely affects the operation of the network
or violates the rules and protocols for communication across the
network.
Corresponding Source conveyed, and Installation Information provided,
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
#### 7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
@@ -351,41 +339,36 @@ apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders
of that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
- a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
- b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
- c) Prohibiting misrepresentation of the origin of that material,
or requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
- d) Limiting the use for publicity purposes of names of licensors
or authors of the material; or
- e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
- f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions
of it) with contractual assumptions of liability to the recipient,
for any liability that these contractual assumptions directly
impose on those licensors and authors.
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
@@ -395,47 +378,47 @@ License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions; the
above requirements apply either way.
8. Termination.
#### 8. Termination.
You may not propagate or modify a covered work except as expressly
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
However, if you cease all violation of this License, then your license
from a particular copyright holder is reinstated (a) provisionally,
unless and until the copyright holder explicitly and finally
terminates your license, and (b) permanently, if the copyright holder
fails to notify you of the violation by some reasonable means prior to
60 days after the cessation.
Moreover, your license from a particular copyright holder is
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
#### 9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
You are not required to accept this License in order to receive or run
a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
@@ -443,14 +426,14 @@ modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
#### 10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
@@ -460,7 +443,7 @@ give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
@@ -468,14 +451,14 @@ rights granted under this License, and you may not initiate litigation
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
#### 11. Patents.
A "contributor" is a copyright holder who authorizes use under this
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
A contributor's "essential patent claims" are all patent claims owned
or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
@@ -484,19 +467,19 @@ purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
@@ -510,7 +493,7 @@ covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
@@ -518,157 +501,160 @@ or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
A patent license is "discriminatory" if it does not include within the
scope of its coverage, prohibits the exercise of, or is conditioned on
the non-exercise of one or more of the rights that are specifically
granted under this License. You may not convey a covered work if you
are a party to an arrangement with a third party that is in the
business of distributing software, under which you make payment to the
third party based on the extent of your activity of conveying the
work, and under which the third party grants, to any of the parties
who would receive the covered work from you, a discriminatory patent
license (a) in connection with copies of the covered work conveyed by
you (or copies made from those copies), or (b) primarily for and in
connection with specific products or compilations that contain the
covered work, unless you entered into that arrangement, or that patent
license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
#### 12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
covered work so as to satisfy simultaneously your obligations under
this License and any other pertinent obligations, then as a
consequence you may not convey it at all. For example, if you agree to
terms that obligate you to collect a royalty for further conveying
from those to whom you convey the Program, the only way you could
satisfy both those terms and this License would be to refrain entirely
from conveying the Program.
13. Use with the GNU Affero General Public License.
#### 13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, you have
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your
version supports such interaction) an opportunity to receive the
Corresponding Source of your version by providing access to the
Corresponding Source from a network server at no charge, through some
standard or customary means of facilitating copying of software. This
Corresponding Source shall include the Corresponding Source for any
work covered by version 3 of the GNU General Public License that is
incorporated pursuant to the following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
#### 14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
The Free Software Foundation may publish revised and/or new versions
of the GNU Affero General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Each version is given a distinguishing version number. If the Program
specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
GNU Affero General Public License, you may choose any version ever
published by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
If the Program specifies that a proxy can decide which future versions
of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
#### 15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
CORRECTION.
16. Limitation of Liability.
#### 16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
#### 17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
### How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
free software which everyone can redistribute and change under these
terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
To do so, attach the following notices to the program. It is safest to
attach them to the start of each source file to most effectively state
the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
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 (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
GNU Affero General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
Also add information on how to contact you by electronic and paper
mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for
the specific requirements.
{project} Copyright (C) {year} {fullname}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
You should also get your employer (if you work as a programmer) or
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. For more information on this, and how to apply and follow
the GNU AGPL, see <https://www.gnu.org/licenses/>.

View File

@@ -1,7 +1,60 @@
# TSBattery
TSBattery a new way to save your battery avoid cancer apps hacker it.<br/>
TSBattery 是一个旨在使 QQ、TIM 变得更省电的开源 Xposed 模块
# Get startted
此模块支持原生 Xposed、Lsposed(作用域 QQ、TIM 如果不起作用勾选系统框架)、EdXposed(不推荐)、太极无极(阴和阳)、Pine(梦境模块)
# 禁止任何商业用途
本模块完全开源免费,如果好用你可以打赏支持开发,严禁未经许可进行二改贩卖,违者必惩必究。
[![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-v3.5-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/static/v1?label=Telegram&message=交流讨论&color=0088cc)](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/>
TSBattery 是一个旨在使 QQ、TIM、微信 变得更省电的开源 Xposed 模块。
# Developer
[酷安 @星夜不荟](http://www.coolapk.com/u/876977)
# 适配说明
- 支持并建议使用 <b>LSPosed</b>(若作用域没有自动出现推荐请勾选 QQ、TIM、微信)
- 可以使用 <b>~~EdXposed~~</b>,但随时停止支持
- <b>太极无极 · 阴</b> 支持性不是很好,建议使用 <b>太极无极 · 阳</b>
- 支持 <b>Pine</b>(梦境模块) 但是部分功能有限制
- 请不要使用 <b>~~应用转生~~</b>,发生封号情况后果自负
# 请勿用于非法用途
- 本模块完全开源免费,如果好用你可以打赏支持开发,但是请不要用于非法用途。
- 本模块发布地址仅有 [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),从其他非正规渠道下载到的版本或对您造成任何影响均与我们无关。
# 开始贡献
欢迎为此项目进行新版本的适配代码贡献!<br/>
- [CONTRIBUTING](https://github.com/fankes/TSBattery/blob/master/CONTRIBUTING.md)
# 许可证
- [AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.html)
```
Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
This program is free 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 (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
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)

1
app/.gitignore vendored
View File

@@ -1 +1,2 @@
/build
/release

View File

@@ -1,12 +1,13 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.devtools.ksp' version '1.6.10-1.0.2'
}
android {
signingConfigs {
debug {
storeFile file('/Users/fankes/ProjectPath/AndroidStudioProjects/TSBattery/keystore/public')
storeFile file('../keystore/public')
storePassword '123456'
keyAlias 'public'
keyPassword '123456'
@@ -14,16 +15,23 @@ android {
v2SigningEnabled true
}
}
compileSdkVersion 30
buildToolsVersion "30.0.3"
compileSdkVersion 31
buildToolsVersion "31.0.0"
defaultConfig {
applicationId "com.fankes.tsbattery"
minSdkVersion 22
//noinspection ExpiredTargetSdkVersion,OldTargetApi
targetSdkVersion 26
versionCode 5
versionName "2.2"
minSdk 22
targetSdk 26
versionCode rootProject.ext.appVersionCode
versionName rootProject.ext.appVersionName
kotlinOptions {
freeCompilerArgs = [
'-Xno-param-assertions',
'-Xno-call-assertions',
'-Xno-receiver-assertions'
]
}
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -42,29 +50,38 @@ android {
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
viewBinding true
}
}
dependencies {
compileOnly 'de.robv.android.xposed:api:82'
// 基础依赖包,必须要依赖
implementation 'com.gyf.immersionbar:immersionbar:3.0.0'
// fragment快速实现可选
implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0'
// kotlin扩展可选
implementation 'com.gyf.immersionbar:immersionbar-ktx:3.0.0'
//noinspection GradleDependency,DifferentStdlibGradleVersion
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
//noinspection GradleDependency
implementation 'androidx.core:core-ktx:1.5.0'
//noinspection GradleDependency
implementation 'androidx.appcompat:appcompat:1.3.0'
//noinspection GradleDependency
implementation 'com.google.android.material:material:1.3.0'
//noinspection GradleDependency
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.highcapable.yukihookapi:api:1.0.6'
ksp 'com.highcapable.yukihookapi:ksp-xposed:1.0.6'
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
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'
testImplementation 'junit:junit:4.13.2'
//noinspection GradleDependency
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
//noinspection GradleDependency
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
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
}

View File

@@ -27,22 +27,26 @@
-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**
-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);
}

Binary file not shown.

View File

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

View File

@@ -3,7 +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" />
</queries>
<application
android:name=".application.TSApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
@@ -12,24 +23,23 @@
android:theme="@style/Theme.TSBattery"
tools:ignore="AllowBackup">
<!-- 是否是xposed模块 -->
<meta-data
android:name="xposedmodule"
android:value="true" />
<!-- 模块描述 -->
<meta-data
android:name="xposeddescription"
android:value="抵制毒瘤,拒绝疯狂耗电,Tencent 社交毒瘤一键省电模块(目前支持 QQ、TIM)通过干掉电源锁常驻减少电量消耗理论支持最新版本。by 酷安 @星夜不荟" />
android:value="Tencent 社交毒瘤一键省电模块。\n目前支持 QQ、TIM、微信\n开发者酷安 @星夜不荟" />
<!-- 最低xposed版本号 -->
<meta-data
android:name="xposedminversion"
android:value="82" />
android:value="93" />
<activity
android:name="com.fankes.tsbattery.MainActivity"
android:label="@string/app_name">
android:name="com.fankes.tsbattery.ui.activity.MainActivity"
android:exported="true"
android:screenOrientation="behind">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="de.robv.android.xposed.category.MODULE_SETTINGS" />
@@ -39,8 +49,11 @@
<activity-alias
android:name="com.fankes.tsbattery.Home"
android:enabled="true"
android:exported="true"
android:label="@string/app_name"
android:targetActivity="com.fankes.tsbattery.MainActivity">
android:screenOrientation="behind"
android:targetActivity="com.fankes.tsbattery.ui.activity.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />

View File

@@ -1 +1 @@
com.fankes.tsbattery.hook.HookMain
com.fankes.tsbattery.hook.HookEntry_YukiHookXposedInit

View File

@@ -0,0 +1 @@
com.fankes.tsbattery.hook.HookEntry

View File

@@ -1,234 +0,0 @@
/*
* Copyright (C) 2021. Fankes Studio(qzmmcn@163.com)
*
* This file is part of TSBattery.
*
* TSBattery is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* TSBattery is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* This file is Created by fankes on 2021/9/4.
*/
@file:Suppress(
"DEPRECATION", "SetTextI18n", "SetWorldReadable", "WorldReadableFiles",
"LocalVariableName"
)
package com.fankes.tsbattery
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.util.Log
import android.view.View
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.Keep
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.widget.SwitchCompat
import androidx.constraintlayout.utils.widget.ImageFilterView
import com.fankes.tsbattery.hook.HookMain
import com.fankes.tsbattery.utils.FileUtils
import com.gyf.immersionbar.ImmersionBar
import java.io.File
@Keep
class MainActivity : AppCompatActivity() {
companion object {
private const val moduleVersion = BuildConfig.VERSION_NAME
private const val moduleSupport = "QQ 8.5.5~8.8.23、TIM 2+"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
/*禁止系统夜间模式对自己造成干扰*/
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
/*隐藏系统的标题栏*/
supportActionBar?.hide()
/*初始化沉浸状态栏*/
ImmersionBar.with(this)
.statusBarColor("#FFFFFFFF")
.autoDarkModeEnable(false)
.statusBarDarkFont(true)
.navigationBarColor("#FFFFFFFF")
.navigationBarDarkIcon(true)
.fitsSystemWindows(true)
.init()
/*判断 Hook 状态*/
if (isHooked()) {
findViewById<LinearLayout>(R.id.main_lin_status).setBackgroundResource(R.drawable.green_round)
findViewById<ImageFilterView>(R.id.main_img_status).setImageResource(R.mipmap.succcess)
findViewById<TextView>(R.id.main_text_status).text = "模块已激活"
} else
AlertDialog.Builder(this)
.setTitle("模块没有激活")
.setMessage(
"检测到模块没有激活,模块需要 Xposed 环境依赖,同时需要系统拥有 Root 权限(太极阴可以免 Root),请自行查看本页面使用帮助与说明第三条。\n" +
"太极、应用转生、梦境(Pine)和第三方 Xposed 激活后可能不会提示激活,若想验证是否激活请打开“提示模块运行信息”自行检查,如果生效就代表模块运行正常,这里的激活状态只是一个显示意义上的存在。"
)
.setPositiveButton("我知道了", null)
.setCancelable(false)
.show()
/*设置文本*/
findViewById<TextView>(R.id.main_text_version).text = "当前版本:$moduleVersion"
findViewById<TextView>(R.id.main_text_support).text = "支持 $moduleSupport"
/*初始化 View*/
val protectModeSwitch = findViewById<SwitchCompat>(R.id.protect_mode_switch)
val hideIconInLauncherSwitch = findViewById<SwitchCompat>(R.id.hide_icon_in_launcher_switch)
val notifyModuleInfoSwitch = findViewById<SwitchCompat>(R.id.notify_module_info_switch)
/*获取 Sp 存储的信息*/
protectModeSwitch.isChecked = getBoolean("_white_mode")
hideIconInLauncherSwitch.isChecked = getBoolean("_hide_icon")
notifyModuleInfoSwitch.isChecked = getBoolean("_tip_run_info")
protectModeSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
putBoolean("_white_mode", b)
}
hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
putBoolean("_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
putBoolean("_tip_run_info", b)
}
/*项目地址点击事件*/
findViewById<View>(R.id.link_with_project_address).setOnClickListener {
try {
val intent = Intent()
intent.action = "android.intent.action.VIEW"
val content_url = Uri.parse("https://github.com/fankes/TSBattery")
intent.data = content_url
startActivity(intent)
} catch (e: Exception) {
Toast.makeText(this, "无法启动系统默认浏览器", Toast.LENGTH_SHORT).show()
}
}
}
/**
* 判断模块是否激活
* 在 [HookMain] 中 Hook 掉此方法
* @return 激活状态
*/
private fun isHooked(): Boolean {
Log.d("TSBattery", "isHooked: true")
return isExpModuleActive()
}
/**
* 新增太极判断方式
* @return 是否激活
*/
private fun isExpModuleActive(): Boolean {
var isExp = false
try {
val uri = Uri.parse("content://me.weishu.exposed.CP/")
var result: Bundle? = null
try {
result = contentResolver.call(uri, "active", null, null)
} catch (e: RuntimeException) {
// TaiChi is killed, try invoke
try {
val intent = Intent("me.weishu.exp.ACTION_ACTIVE")
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
} catch (e1: Throwable) {
return false
}
}
if (result == null) result = contentResolver.call(uri, "active", null, null)
if (result == null) return false
isExp = result.getBoolean("active", false)
} catch (ignored: Throwable) {
}
return isExp
}
override fun onResume() {
super.onResume()
setWorldReadable()
}
override fun onRestart() {
super.onRestart()
setWorldReadable()
}
override fun onPause() {
super.onPause()
setWorldReadable()
}
/**
* 获取保存的值
* @param key 名称
* @return 保存的值
*/
private fun getBoolean(key: String) =
getSharedPreferences(
packageName + "_preferences",
Context.MODE_PRIVATE
).getBoolean(key, false)
/**
* 保存值
* @param key 名称
* @param bool 值
*/
private fun putBoolean(key: String, bool: Boolean) {
getSharedPreferences(
packageName + "_preferences",
Context.MODE_PRIVATE
).edit().putBoolean(key, bool).apply()
setWorldReadable()
Handler().postDelayed({ setWorldReadable() }, 500)
Handler().postDelayed({ setWorldReadable() }, 1000)
Handler().postDelayed({ setWorldReadable() }, 1500)
}
/**
* 强制设置 Sp 存储为全局可读可写
* 以供模块使用
*/
private fun setWorldReadable() {
try {
if (FileUtils.getDefaultPrefFile(this).exists()) {
for (file in arrayOf<File>(
FileUtils.getDataDir(this),
FileUtils.getPrefDir(this),
FileUtils.getDefaultPrefFile(this)
)) {
file.setReadable(true, false)
file.setExecutable(true, false)
}
}
} catch (e: Exception) {
Toast.makeText(this, "无法写入模块设置,请检查权限\n如果此提示一直显示,请不要双开模块", Toast.LENGTH_SHORT).show()
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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.
*/
@file:Suppress("unused")
package com.fankes.tsbattery.application
import android.app.Application
import androidx.appcompat.app.AppCompatDelegate
class TSApplication : Application() {
companion object {
/** 全局静态实例 */
private var context: TSApplication? = null
/**
* 调用全局静态实例
* @return [TSApplication]
*/
val appContext get() = context ?: error("App is death")
}
override fun onCreate() {
super.onCreate()
/** 设置静态实例 */
context = this
/** 跟随系统夜间模式 */
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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

@@ -0,0 +1,522 @@
/*
* 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/2/15.
*/
@file:Suppress("IMPLICIT_CAST_TO_ANY")
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.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
@InjectYukiHookWithXposed
class HookEntry : YukiHookXposedInitProxy {
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 {
isQQTIM -> SplashActivityClass.hook {
/**
* Hook 启动界面的第一个 [Activity]
* QQ 和 TIM 都是一样的类
* 在里面加入提示运行信息的对话框测试模块是否激活
*/
injectMember {
method {
name = "doOnCreate"
param(BundleClass)
}
afterHook {
if (prefs.getBoolean(ENABLE_RUN_INFO))
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 {
if (prefs.getBoolean(ENABLE_RUN_INFO))
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) {
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 {
if (prefs.getBoolean(ENABLE_QQTIM_CORESERVICE_BAN))
instance<Service>().apply {
stopForeground(true)
stopService(Intent(applicationContext, javaClass))
loggerD(msg = "Shutdown CoreService OK!")
}
}
}
}
CoreService_KernelServiceClass.hook {
injectMember {
method { name = "onCreate" }
afterHook {
if (prefs.getBoolean(ENABLE_QQTIM_CORESERVICE_CHILD_BAN))
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)
}
}
}
override fun onInit() = configs {
debugTag = "TSBattery"
isDebug = false
isEnableModulePrefsCache = 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
/** 通过在 [SplashActivityClass] 里取到应用的版本号 */
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()
}.ignoredHookClassNotFoundFailure()
/**
* 一个不知道是什么作用的电源锁
* 同样直接干掉
*/
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()
}.ignoredHookClassNotFoundFailure()
/**
* 干掉消息收发功能的电源锁
* 每个版本的差异暂未做排查
* 旧版本理论上没有这个类
*/
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") }
}.ignoredHookClassNotFoundFailure()
/**
* 干掉自动上传服务的电源锁
* 每个版本的差异暂未做排查
*/
findClass(name = "com.tencent.upload.impl.UploadServiceImpl").hook {
injectMember {
method { name = "acquireWakeLockIfNot" }
intercept()
}.onAllFailure { loggerE(msg = "Hook UploadServiceImpl Failed $it") }
}.ignoredHookClassNotFoundFailure()
/**
* Hook 掉一个一像素保活 [Activity] 真的我怎么都想不到讯哥的程序员做出这种事情
* 这个东西经过测试会在锁屏的时候吊起来,解锁的时候自动 finish(),无限耍流氓耗电
* 2022/1/25 后期查证:锁屏界面消息快速回复窗口的解锁后拉起保活界面,也是毒瘤
*/
findClass(name = "$QQ_PACKAGE_NAME.activity.QQLSUnlockActivity").hook {
injectMember {
method {
name = "onCreate"
param(BundleClass)
}
var origDevice = ""
beforeHook {
/** 由于在 onCreate 里有一行判断只要型号是 xiaomi 的设备就开电源锁,所以说这里临时替换成菊花厂 */
origDevice = Build.MANUFACTURER
if (Build.MANUFACTURER.lowercase() == "xiaomi")
BuildClass.field { name = "MANUFACTURER" }.get().set("HUAWEI")
}
afterHook {
instance<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()
}
injectMember {
method {
name = "handleMessage"
param(MessageClass)
}
replaceToFalse()
}
}.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 = "ウイチャット:それが機能するかどうかはわかりませんでした")
}
}
}

View File

@@ -1,366 +0,0 @@
/*
* Copyright (C) 2021. Fankes Studio(qzmmcn@163.com)
*
* This file is part of TSBattery.
*
* TSBattery is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* TSBattery is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* This file is Created by fankes on 2021/9/4.
*/
@file:Suppress("DEPRECATION", "SameParameterValue")
package com.fankes.tsbattery.hook
import android.app.Activity
import android.app.AlertDialog
import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.annotation.Keep
import com.fankes.tsbattery.utils.XPrefUtils
import de.robv.android.xposed.*
import de.robv.android.xposed.callbacks.XC_LoadPackage
import java.util.*
@Keep
class HookMain : IXposedHookLoadPackage {
/**
* Print the log
* @param content
*/
private fun logD(content: String) {
XposedBridge.log(content)
Log.d("TSBattery", content)
}
/**
* Print the log
* @param content
*/
private fun logE(content: String, e: Throwable? = null) {
XposedBridge.log(content)
Log.e("TSBattery", content, e)
}
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam?) {
if (lpparam == null) return
when (lpparam.packageName) {
/*Hook 自身*/
"com.fankes.tsbattery" ->
XposedHelpers.findAndHookMethod(
"com.fankes.tsbattery.MainActivity",
lpparam.classLoader,
"isHooked",
object : XC_MethodReplacement() {
override fun replaceHookedMethod(param: MethodHookParam?): Any {
return true
}
})
/*经过测试 QQ 与 TIM 这两个是一个模子里面的东西,所以他们的类名也基本上是一样的*/
"com.tencent.mobileqq", "com.tencent.tim" -> {
try {
XposedHelpers.findAndHookMethod(
"android.os.PowerManager\$WakeLock",
lpparam.classLoader,
"acquire",
object : XC_MethodReplacement() {
override fun replaceHookedMethod(param: MethodHookParam?): Any? {
return null
}
})
} catch (e: Throwable) {
logE("handleLoadPackage: hook wakeLock acquire() Failed", e)
}
try {
XposedHelpers.findAndHookMethod(
"android.os.PowerManager\$WakeLock",
lpparam.classLoader,
"acquire",
Long::class.java,
object : XC_MethodReplacement() {
override fun replaceHookedMethod(param: MethodHookParam?): Any? {
return null
}
})
} catch (e: Throwable) {
logE("handleLoadPackage: hook wakeLock acquire(time) Failed", e)
}
/*判断是否开启提示模块运行信息*/
if (XPrefUtils.getBoolean("_tip_run_info"))
try {
/**
* Hook 启动界面的第一个 [Activity]
* QQ 和 TIM 都是一样的类
* 在里面加入提示运行信息的对话框测试模块是否激活
*/
XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.SplashActivity",
lpparam.classLoader,
"doOnCreate",
Bundle::class.java,
object : XC_MethodHook() {
override fun afterHookedMethod(param: MethodHookParam?) {
val self = param!!.thisObject as Activity
AlertDialog.Builder(
self,
android.R.style.Theme_Material_Dialog_Alert
).setCancelable(false)
.setTitle("TSBattery 已激活")
.setMessage(
"模块工作看起来一切正常,请自行测试是否能达到省电效果。\n\n" +
"当前模式:${if (XPrefUtils.getBoolean("_white_mode")) "保守模式" else "完全模式"}" +
"\n\n包名:${self.packageName}\n版本:${
self.packageManager.getPackageInfo(
self.packageName,
0
).versionName
}(${
self.packageManager.getPackageInfo(
self.packageName,
0
).versionCode
})" + "\n\nPS模块只对挂后台锁屏情况下有省电效果请不要将过多的群提醒消息通知打开这样子在使用过程时照样会极其耗电\n" +
"如果你不想看到此提示。请在模块设置中关闭运行信息提醒,此设置默认关闭。\n" +
"开发者 酷安 @星夜不荟\n未经允许禁止转载、修改或复制我的劳动成果。"
)
.setPositiveButton("我知道了", null)
.show()
}
})
} catch (e: Exception) {
logE("handleLoadPackage: hook SplashActivity Failed", e)
}
/*关闭保守模式后不再仅仅作用于系统电源锁*/
if (!XPrefUtils.getBoolean("_white_mode")) {
val replaceMent = object : XC_MethodReplacement() {
override fun replaceHookedMethod(param: MethodHookParam?): Any? {
return null
}
}
/**
* 这个类 BaseChatPie 是控制聊天界面的
* 里面有两个随机混淆的方法
* 这两个方法一个是挂起电源锁常驻亮屏
* 一个是停止常驻亮屏
* 不由分说每个版本混淆的方法名都会变
* 所以说每个版本重新适配 - 也可以提交分支帮我适配
* 8.8.17 版本是 bd be
* 8.8.23 版本是 bf bg
* ⚠️ Hook 错了方法会造成闪退!
*/
try {
/*通过在 SplashActivity 里取到应用的版本号*/
XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.SplashActivity",
lpparam.classLoader,
"doOnCreate",
Bundle::class.java,
object : XC_MethodHook() {
override fun beforeHookedMethod(param: MethodHookParam?) {
val self = param!!.thisObject as Activity
val name = self.packageName
val version =
self.packageManager.getPackageInfo(name, 0).versionName
/*这个地方我们只处理 QQ*/
try {
if (name == "com.tencent.mobileqq") {
when (version) {
"8.8.17" -> {
XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.aio.core.BaseChatPie",
lpparam.classLoader,
"bd",
replaceMent
)
XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.aio.core.BaseChatPie",
lpparam.classLoader,
"be",
replaceMent
)
}
"8.8.23" -> {
XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.aio.core.BaseChatPie",
lpparam.classLoader,
"bf",
replaceMent
)
XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.aio.core.BaseChatPie",
lpparam.classLoader,
"bg",
replaceMent
)
}
//TODO 后面的版本逐个适配 此方法没封装 目前比较笨蛋 主要是我懒得写
}
}
} catch (e: Exception) {
logE("handleLoadPackage: hook BaseChatPie Failed", e)
}
}
})
} catch (e: Exception) {
logE("handleLoadPackage: hook BaseChatPie(first time) Failed", e)
}
try {
/**
* 一个不知道是什么作用的电源锁
* 同样直接干掉
*/
XposedHelpers.findAndHookMethod(
"com.tencent.mars.ilink.comm.WakerLock",
lpparam.classLoader,
"lock", Long::class.java,
replaceMent
)
} catch (e: Exception) {
logE("handleLoadPackage: hook WakerLock Failed", e)
}
try {
/**
* Hook 掉一个一像素保活 [Activity] 真的我怎么都想不到讯哥的程序员做出这种事情
* 这个东西经过测试会在锁屏的时候吊起来,解锁的时候自动 finish(),无限耍流氓耗电
*/
XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.QQLSUnlockActivity",
lpparam.classLoader,
"onCreate", Bundle::class.java,
object : XC_MethodHook() {
private var origDevice = ""
override fun beforeHookedMethod(param: MethodHookParam?) {
/*由于在 onCreate 里有一行判断只要型号是 xiaomi 的设备就开电源锁,所以说这里临时替换成菊花厂*/
origDevice = Build.MANUFACTURER
if (Build.MANUFACTURER.toLowerCase(Locale.ROOT) == "xiaomi")
XposedHelpers.setStaticObjectField(
Build::class.java,
"MANUFACTURER",
"HUAWEI"
)
}
override fun afterHookedMethod(param: MethodHookParam?) {
(param?.thisObject as? Activity)?.finish()
/*这里再把型号替换回去 - 不影响应用变量等 Xposed 模块修改的型号*/
XposedHelpers.setStaticObjectField(
Build::class.java,
"MANUFACTURER",
origDevice
)
}
}
)
/**
* 这个东西同上,不知道是啥时候调用
* 反正也是一个一像素保活的 [Activity]
* 讯哥的程序员真的有你的
*/
XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.QQLSActivity\$14",
lpparam.classLoader,
"run",
replaceMent
)
} catch (e: Exception) {
logE("handleLoadPackage: hook QQLSActivity Failed", e)
}
try {
/**
* 这个是毒瘤核心类
* WakeLockMonitor
* 这个名字真的起的特别诗情画意
* 带给用户的却是 shit 一样的体验
* 里面有各种使用 Handler 和 Timer 的各种耗时常驻后台耗电办法持续接收消息
* 直接循环全部方法全部干掉
*/
lpparam.classLoader.loadClass("com.tencent.qapmsdk.qqbattery.monitor.WakeLockMonitor")
.apply {
val lockClazz =
lpparam.classLoader.loadClass("com.tencent.qapmsdk.qqbattery.monitor.WakeLockMonitor\$WakeLockEntity")
val hookClazz =
lpparam.classLoader.loadClass("com.tencent.qapmsdk.qqbattery.monitor.MethodHookParam")
val onHook = getDeclaredMethod(
"onHook",
String::class.java,
Any::class.java,
java.lang.reflect.Array.newInstance(
Any::class.java,
0
).javaClass,
Any::class.java
).apply { isAccessible = true }
val doReport =
getDeclaredMethod(
"doReport",
lockClazz,
Int::class.java
).apply {
isAccessible = true
}
val afterHookedMethod =
getDeclaredMethod(
"afterHookedMethod",
hookClazz
).apply { isAccessible = true }
val beforeHookedMethod =
getDeclaredMethod("beforeHookedMethod", hookClazz).apply {
isAccessible = true
}
val onAppBackground =
getDeclaredMethod("onAppBackground").apply {
isAccessible = true
}
val onOtherProcReport =
getDeclaredMethod(
"onOtherProcReport",
Bundle::class.java
).apply { isAccessible = true }
val onProcessRun30Min =
getDeclaredMethod("onProcessRun30Min").apply {
isAccessible = true
}
val onProcessBG5Min =
getDeclaredMethod("onProcessBG5Min").apply {
isAccessible = true
}
val writeReport =
getDeclaredMethod(
"writeReport",
Boolean::class.java
).apply { isAccessible = true }
XposedBridge.hookMethod(onHook, replaceMent)
XposedBridge.hookMethod(doReport, replaceMent)
XposedBridge.hookMethod(afterHookedMethod, replaceMent)
XposedBridge.hookMethod(beforeHookedMethod, replaceMent)
XposedBridge.hookMethod(onAppBackground, replaceMent)
XposedBridge.hookMethod(onOtherProcReport, replaceMent)
XposedBridge.hookMethod(onProcessRun30Min, replaceMent)
XposedBridge.hookMethod(onProcessBG5Min, replaceMent)
XposedBridge.hookMethod(writeReport, replaceMent)
}
} catch (e: Throwable) {
logE("handleLoadPackage: hook WakerLockMonitor Failed", e)
}
logD("handleLoadPackage: hook Complete!")
}
}
}
}
}

View File

@@ -0,0 +1,213 @@
/*
* 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.pm.PackageManager
import android.view.HapticFeedbackConstants
import androidx.core.view.isVisible
import com.fankes.tsbattery.BuildConfig
import com.fankes.tsbattery.R
import com.fankes.tsbattery.databinding.ActivityMainBinding
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.ui.activity.base.BaseActivity
import com.fankes.tsbattery.utils.factory.*
import com.fankes.tsbattery.utils.tool.GithubReleaseTool
import com.highcapable.yukihookapi.hook.factory.isModuleActive
import com.highcapable.yukihookapi.hook.factory.isTaiChiModuleActive
import com.highcapable.yukihookapi.hook.factory.modulePrefs
import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus
class MainActivity : BaseActivity<ActivityMainBinding>() {
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 = "全版本仅支持基础省电,更多功能依然画饼"
/** 预发布的版本标识 */
private const val pendingFlag = ""
}
override fun onCreate() {
/** 检查更新 */
GithubReleaseTool.checkingForUpdate(context = this, moduleVersion) { version, function ->
binding.mainTextReleaseVersion.apply {
text = "点击更新 $version"
isVisible = true
setOnClickListener { function() }
}
}
/** 判断 Hook 状态 */
if (isModuleActive) {
binding.mainLinStatus.setBackgroundResource(R.drawable.bg_green_round)
binding.mainImgStatus.setImageResource(R.mipmap.ic_success)
binding.mainTextStatus.text = "模块已激活"
binding.mainTextApiWay.isVisible = true
refreshActivateExecutor()
/** 写入激活的模块版本 */
modulePrefs.putString(ENABLE_MODULE_VERSION, moduleVersion)
} else
showDialog {
title = "模块没有激活"
msg = "检测到模块没有激活,模块需要 Xposed 环境依赖," +
"同时需要系统拥有 Root 权限(太极阴可以免 Root)" +
"请自行查看本页面使用帮助与说明第三条。\n" +
"太极和第三方 Xposed 激活后" +
"可能不会提示激活,若想验证是否激活请打开“提示模块运行信息”自行检查," +
"或观察 QQ、TIM 的常驻通知是否有“TSBattery 守护中”字样”。\n\n" +
"如果生效就代表模块运行正常,若你在未 Root 情况下激活模块,这里的激活状态只是一个显示意义上的存在。\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()
}
/** 设置安装状态 */
binding.mainTextQqVer.text = if (QQ_PACKAGE_NAME.isInstall) version(QQ_PACKAGE_NAME) else "未安装"
binding.mainTextTimVer.text = if (TIM_PACKAGE_NAME.isInstall) version(TIM_PACKAGE_NAME) else "未安装"
binding.mainTextWechatVer.text = if (WECHAT_PACKAGE_NAME.isInstall) version(WECHAT_PACKAGE_NAME) else "未安装"
/** 设置文本 */
binding.mainTextVersion.text = "模块版本:$moduleVersion $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)
}
/** 获取 Sp 存储的信息 */
binding.qqtimProtectModeSwitch.isChecked = modulePrefs.getBoolean(ENABLE_QQTIM_WHITE_MODE)
binding.qqTimCoreServiceSwitch.isChecked = modulePrefs.getBoolean(ENABLE_QQTIM_CORESERVICE_BAN)
binding.qqTimCoreServiceKnSwitch.isChecked = modulePrefs.getBoolean(ENABLE_QQTIM_CORESERVICE_CHILD_BAN)
binding.wechatDisableHookSwitch.isChecked = modulePrefs.getBoolean(DISABLE_WECHAT_HOOK)
binding.hideIconInLauncherSwitch.isChecked = modulePrefs.getBoolean(ENABLE_HIDE_ICON)
binding.notifyModuleInfoSwitch.isChecked = modulePrefs.getBoolean(ENABLE_RUN_INFO)
binding.notifyNotifyTipSwitch.isChecked = modulePrefs.getBoolean(ENABLE_NOTIFY_TIP, default = true)
binding.qqtimProtectModeSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(ENABLE_QQTIM_WHITE_MODE, b)
snake(msg = "修改需要重启 QQ 以生效")
}
binding.qqTimCoreServiceSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(ENABLE_QQTIM_CORESERVICE_BAN, b)
}
binding.qqTimCoreServiceKnSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(ENABLE_QQTIM_CORESERVICE_CHILD_BAN, b)
}
binding.wechatDisableHookSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(DISABLE_WECHAT_HOOK, b)
snake(msg = "修改需要重启微信以生效")
}
binding.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
)
}
binding.notifyModuleInfoSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(ENABLE_RUN_INFO, b)
}
binding.notifyNotifyTipSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(ENABLE_NOTIFY_TIP, b)
}
/** 快捷操作 QQ */
binding.quickQqButton.setOnClickListener { openSelfSetting(QQ_PACKAGE_NAME) }
/** 快捷操作 TIM */
binding.quickTimButton.setOnClickListener { openSelfSetting(TIM_PACKAGE_NAME) }
/** 快捷操作微信 */
binding.quickWechatButton.setOnClickListener { openSelfSetting(WECHAT_PACKAGE_NAME) }
/** 项目地址按钮点击事件 */
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")
}
}
/** 刷新模块激活使用的方式 */
private fun refreshActivateExecutor() {
when {
YukiHookModuleStatus.executorVersion > 0 ->
binding.mainTextApiWay.text =
"Activated by ${YukiHookModuleStatus.executorName} API ${YukiHookModuleStatus.executorVersion}"
isTaiChiModuleActive -> binding.mainTextApiWay.text = "Activated by TaiChi"
else -> binding.mainTextApiWay.text = "Activated by anonymous"
}
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.viewbinding.ViewBinding
import com.fankes.tsbattery.R
import com.fankes.tsbattery.utils.factory.isNotSystemInDarkMode
import com.gyf.immersionbar.ktx.immersionBar
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
import java.lang.reflect.ParameterizedType
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
/** 获取绑定布局对象 */
lateinit var binding: VB
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
javaClass.genericSuperclass.also { type ->
if (type is ParameterizedType) {
binding = (type.actualTypeArguments[0] as Class<VB>).method {
name = "inflate"
param(LayoutInflaterClass)
}.get().invoke<VB>(layoutInflater) ?: error("binding failed")
setContentView(binding.root)
} else error("binding but got wrong type")
}
/** 隐藏系统的标题栏 */
supportActionBar?.hide()
/** 初始化沉浸状态栏 */
immersionBar {
statusBarColor(R.color.colorThemeBackground)
autoDarkModeEnable(true)
statusBarDarkFont(isNotSystemInDarkMode)
navigationBarColor(R.color.colorThemeBackground)
navigationBarDarkIcon(isNotSystemInDarkMode)
fitsSystemWindows(true)
}
/** 装载子类 */
onCreate()
}
/** 回调 [onCreate] 方法 */
abstract fun onCreate()
}

View File

@@ -0,0 +1,71 @@
/*
* 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.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
class MaterialSwitch(context: Context, attrs: AttributeSet?) : SwitchCompat(context, attrs) {
private fun toColors(selected: Int, pressed: Int, normal: Int): ColorStateList {
val colors = intArrayOf(selected, pressed, normal)
val states = arrayOfNulls<IntArray>(3)
states[0] = intArrayOf(android.R.attr.state_checked)
states[1] = intArrayOf(android.R.attr.state_pressed)
states[2] = intArrayOf()
return ColorStateList(states, colors)
}
init {
trackDrawable = DrawableBuilder()
.rectangle()
.rounded()
.solidColor(0xFF656565.toInt())
.height(20.dp(context))
.cornerRadius(15.dp(context))
.build()
thumbDrawable = DrawableBuilder()
.rectangle()
.rounded()
.solidColor(Color.WHITE)
.size(20.dp(context), 20.dp(context))
.cornerRadius(20.dp(context))
.strokeWidth(8.dp(context))
.strokeColor(Color.TRANSPARENT)
.build()
trackTintList = toColors(
0xFF656565.toInt(),
0xFFCCCCCC.toInt(),
0xFFCCCCCC.toInt()
)
isSingleLine = true
ellipsize = TextUtils.TruncateAt.END
}
}

View File

@@ -1,94 +0,0 @@
/*
* Copyright (C) 2021. Fankes Studio(qzmmcn@163.com)
*
* This file is part of TSBattery.
*
* TSBattery is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* TSBattery is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* This file is Created by zpp0196 on 2019/2/9.
*/
package com.fankes.tsbattery.utils;
import android.content.Context;
import android.os.Environment;
import com.fankes.tsbattery.BuildConfig;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
@SuppressWarnings("ALL")
public class FileUtils {
private static final String FILE_PREF_NAME = BuildConfig.APPLICATION_ID + "_preferences.xml";
public static boolean copyFile(File srcFile, File targetFile) {
FileInputStream ins = null;
FileOutputStream out = null;
try {
if (targetFile.exists()) {
targetFile.delete();
}
File targetParent = targetFile.getParentFile();
if (!targetParent.exists()) {
targetParent.mkdirs();
}
targetFile.createNewFile();
ins = new FileInputStream(srcFile);
out = new FileOutputStream(targetFile);
byte[] b = new byte[1024];
int n;
while ((n = ins.read(b)) != -1) {
out.write(b, 0, n);
}
} catch (IOException e) {
e.printStackTrace();
return false;
} finally {
try {
if (ins != null) {
ins.close();
}
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
public static File getDataDir(Context context) {
return new File(context.getApplicationInfo().dataDir);
}
public static File getPrefDir(Context context) {
return new File(getDataDir(context), "shared_prefs");
}
public static File getDefaultPrefFile(Context context) {
return new File(getPrefDir(context), FILE_PREF_NAME);
}
public static File getBackupPrefsFile() {
return new File(getBackupDir(), FILE_PREF_NAME);
}
private static File getBackupDir() {
return new File(Environment.getExternalStorageDirectory(), "QQPurify");
}
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright (C) 2021. Fankes Studio(qzmmcn@163.com)
*
* This file is part of TSBattery.
*
* TSBattery is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* TSBattery is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* This file is Created by zpp0196 on 2018/4/11.
*/
package com.fankes.tsbattery.utils
import de.robv.android.xposed.XSharedPreferences
object XPrefUtils {
fun getBoolean(key: String) = pref.getBoolean(key, false)
private val pref: XSharedPreferences
get() {
val preferences = XSharedPreferences("com.fankes.tsbattery")
preferences.makeWorldReadable()
preferences.reload()
return preferences
}
}

View File

@@ -0,0 +1,292 @@
/*
* 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

@@ -0,0 +1,29 @@
/*
* 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

@@ -0,0 +1,489 @@
/*
* 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

@@ -0,0 +1,221 @@
/*
* 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

@@ -0,0 +1,34 @@
/*
* 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
abstract class DrawableWrapperBuilder<T: DrawableWrapperBuilder<T>> {
protected var drawable: Drawable? = null
@Suppress("UNCHECKED_CAST")
fun drawable(drawable: Drawable): T = apply { this.drawable = drawable } as T
abstract fun build(): Drawable
}

View File

@@ -0,0 +1,78 @@
/*
* 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

@@ -0,0 +1,35 @@
/*
* 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

@@ -0,0 +1,182 @@
/*
* 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

@@ -0,0 +1,61 @@
/*
* 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

@@ -0,0 +1,73 @@
/*
* 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

@@ -0,0 +1,52 @@
/*
* 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

@@ -0,0 +1,45 @@
/*
* 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

@@ -0,0 +1,60 @@
/*
* 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

@@ -0,0 +1,199 @@
/*
* 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/7.
*/
@file:Suppress("unused", "OPT_IN_USAGE", "EXPERIMENTAL_API_USAGE")
package com.fankes.tsbattery.utils.factory
import android.app.Dialog
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.GradientDrawable
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.viewbinding.ViewBinding
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.highcapable.yukihookapi.annotation.DoNotUseField
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
/**
* 构造对话框
* @param isUseBlackTheme 是否使用深色主题
* @param it 对话框方法体
*/
fun Context.showDialog(isUseBlackTheme: Boolean = false, it: DialogBuilder.() -> Unit) =
DialogBuilder(this, isUseBlackTheme).apply(it).show()
/**
* 对话框构造器
* @param context 实例
* @param isUseBlackTheme 是否使用深色主题 - 对 AndroidX 风格无效
*/
class DialogBuilder(val context: Context, private val isUseBlackTheme: Boolean) {
private var instanceAndroidX: androidx.appcompat.app.AlertDialog.Builder? = null // 实例对象
private var instanceAndroid: android.app.AlertDialog.Builder? = null // 实例对象
private var dialogInstance: Dialog? = null // 对话框实例
@DoNotUseField
var customLayoutView: View? = null // 自定义布局
/**
* 是否需要使用 AndroidX 风格对话框
* @return [Boolean]
*/
private val isUsingAndroidX get() = runCatching { context is AppCompatActivity }.getOrNull() ?: false
init {
if (isUsingAndroidX)
runInSafe { instanceAndroidX = MaterialAlertDialogBuilder(context) }
else runInSafe {
instanceAndroid = android.app.AlertDialog.Builder(
context,
if (isUseBlackTheme) android.R.style.Theme_Material_Dialog else android.R.style.Theme_Material_Light_Dialog
)
}
}
/** 设置对话框不可关闭 */
fun noCancelable() {
if (isUsingAndroidX)
runInSafe { instanceAndroidX?.setCancelable(false) }
else runInSafe { instanceAndroid?.setCancelable(false) }
}
/** 设置对话框标题 */
var title
get() = ""
set(value) {
if (isUsingAndroidX)
runInSafe { instanceAndroidX?.setTitle(value) }
else runInSafe { instanceAndroid?.setTitle(value) }
}
/** 设置对话框消息内容 */
var msg
get() = ""
set(value) {
if (isUsingAndroidX)
runInSafe { instanceAndroidX?.setMessage(value) }
else runInSafe { instanceAndroid?.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
}
/**
* 设置对话框自定义布局
* @return [ViewBinding]
*/
inline fun <reified T : ViewBinding> bind() =
T::class.java.method {
name = "inflate"
param(LayoutInflaterClass)
}.get().invoke<T>(LayoutInflater.from(context))?.apply {
customLayoutView = root
} ?: error("binding failed")
/**
* 设置对话框确定按钮
* @param text 按钮文本内容
* @param it 点击事件
*/
fun confirmButton(text: String = "确定", it: () -> Unit = {}) {
if (isUsingAndroidX)
runInSafe { instanceAndroidX?.setPositiveButton(text) { _, _ -> it() } }
else runInSafe { instanceAndroid?.setPositiveButton(text) { _, _ -> it() } }
}
/**
* 设置对话框取消按钮
* @param text 按钮文本内容
* @param it 点击事件
*/
fun cancelButton(text: String = "取消", it: () -> Unit = {}) {
if (isUsingAndroidX)
runInSafe { instanceAndroidX?.setNegativeButton(text) { _, _ -> it() } }
else runInSafe { instanceAndroid?.setNegativeButton(text) { _, _ -> it() } }
}
/**
* 设置对话框第三个按钮
* @param text 按钮文本内容
* @param it 点击事件
*/
fun neutralButton(text: String = "更多", it: () -> Unit = {}) {
if (isUsingAndroidX)
runInSafe { instanceAndroidX?.setNeutralButton(text) { _, _ -> it() } }
else runInSafe { instanceAndroid?.setNeutralButton(text) { _, _ -> it() } }
}
/** 取消对话框 */
fun cancel() = dialogInstance?.cancel()
/** 显示对话框 */
internal fun show() =
if (isUsingAndroidX) runInSafe {
instanceAndroidX?.create()?.apply {
customLayoutView?.let { setView(it) }
dialogInstance = this
}?.show()
} else runInSafe {
instanceAndroid?.create()?.apply {
customLayoutView?.let { setView(it) }
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)
})
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

@@ -0,0 +1,169 @@
/*
* 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/7.
*/
@file:Suppress("DEPRECATION")
package com.fankes.tsbattery.utils.factory
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.Color
import android.net.ConnectivityManager
import android.net.Uri
import android.provider.Settings
import android.widget.Toast
import androidx.core.content.getSystemService
import com.fankes.tsbattery.application.TSApplication.Companion.appContext
import com.google.android.material.snackbar.Snackbar
/**
* 系统深色模式是否开启
* @return [Boolean] 是否开启
*/
val isSystemInDarkMode
get() = (appContext.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
/**
* 系统深色模式是否没开启
* @return [Boolean] 是否开启
*/
inline val isNotSystemInDarkMode get() = !isSystemInDarkMode
/**
* 得到安装包信息
* @return [PackageInfo]
*/
val Context.packageInfo get() = packageManager?.getPackageInfo(packageName, 0) ?: PackageInfo()
/**
* 判断应用是否安装
* @return [Boolean]
*/
val String.isInstall
get() = try {
appContext.packageManager.getPackageInfo(
this,
PackageManager.GET_UNINSTALLED_PACKAGES
)
true
} catch (e: Exception) {
false
}
/**
* 得到版本信息
* @return [String]
*/
val Context.versionName get() = packageInfo.versionName ?: ""
/**
* 得到版本号
* @return [Int]
*/
val Context.versionCode get() = packageInfo.versionCode
/**
* 得到版本信息与版本号
* @param packageName 包名
* @return [String]
*/
fun Context.version(packageName: String) = safeOfNothing {
packageManager?.getPackageInfo(packageName, 0)?.let {
"${it.versionName}(${it.versionCode})"
} ?: ""
}
/**
* 网络连接是否正常
* @return [Boolean] 网络是否连接
*/
val isNetWorkSuccess
get() = safeOfFalse { appContext.getSystemService<ConnectivityManager>()?.activeNetworkInfo != null }
/**
* dp 转换为 pxInt
* @param context 使用的实例
* @return [Int]
*/
fun Number.dp(context: Context) = dpFloat(context).toInt()
/**
* dp 转换为 pxFloat
* @param context 使用的实例
* @return [Float]
*/
fun Number.dpFloat(context: Context) = toFloat() * context.resources.displayMetrics.density
/**
* 弹出 [Toast]
* @param msg 提示内容
*/
fun toast(msg: String) = Toast.makeText(appContext, msg, Toast.LENGTH_SHORT).show()
/**
* 弹出 [Snackbar]
* @param msg 提示内容
* @param actionText 按钮文本 - 不写默认取消按钮
* @param it 按钮事件回调
*/
fun Context.snake(msg: String, actionText: String = "", it: () -> Unit = {}) =
Snackbar.make((this as Activity).findViewById(android.R.id.content), msg, Snackbar.LENGTH_LONG).apply {
if (actionText.isBlank()) return@apply
setActionTextColor(Color.WHITE)
setAction(actionText) { it() }
}.show()
/**
* 跳转 APP 自身设置界面
* @param packageName 包名
*/
fun Context.openSelfSetting(packageName: String = appContext.packageName) = runCatching {
if (packageName.isInstall)
startActivity(Intent().apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
data = Uri.fromParts("package", packageName, null)
})
else toast(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 {
if (packageName.isNotBlank()) snake(msg = "启动 $packageName 失败")
else snake(msg = "启动系统浏览器失败")
}

View File

@@ -0,0 +1,127 @@
/*
* 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.
*/
package com.fankes.tsbattery.utils.tool
import android.app.Activity
import android.content.Context
import com.fankes.tsbattery.utils.factory.*
import okhttp3.*
import org.json.JSONObject
import java.io.IOException
import java.io.Serializable
/**
* 获取 Github Release 最新版本工具类
*/
object GithubReleaseTool {
/** 仓库作者 */
private const val REPO_AUTHOR = "fankes"
/** 仓库名称 */
private const val REPO_NAME = "TSBattery"
/**
* 获取最新版本信息
* @param context 实例
* @param version 当前版本
* @param it 成功后回调 - ([String] 最新版本,[Function] 更新对话框方法体)
*/
fun checkingForUpdate(context: Context, version: String, it: (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").replace(oldValue = "T", newValue = " ").replace(oldValue = "Z", newValue = "")
).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()
it(name) { showUpdate() }
}
}
}
}
})
}
/**
* 检查网络连接情况
* @param context 实例
* @param it 已连接回调
*/
private fun checkingInternetConnect(context: Context, it: () -> Unit) = runInSafe {
if (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 { 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,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#66A6A6A6" />
<corners android:radius="15dp" />
</shape>

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="#666E6E6E" />
<corners android:radius="15dp" />
</shape>

View File

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

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="#661B1B1B" />
<corners android:radius="15dp" />
</shape>

View File

@@ -1,6 +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="#6A6A6A" />
<solid android:color="#FF7043" />
<corners android:radius="15dp" />
</shape>

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="#66E4E4E4" />
<corners android:radius="15dp" />
</shape>

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="#FF786F" />
<corners android:radius="30dp" />
</shape>

View File

@@ -1,36 +1,55 @@
<?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="match_parent"
android:background="@color/colorThemeBackground"
android:orientation="vertical"
tools:context=".MainActivity"
tools:ignore="HardcodedText">
tools:context=".ui.activity.MainActivity"
tools:ignore="HardcodedText,UseCompoundDrawables,ContentDescription,TooManyViews">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:elevation="5dp"
android:padding="15dp">
android:elevation="0dp"
android:gravity="center|start"
android:paddingLeft="15dp"
android:paddingTop="13dp"
android:paddingRight="15dp"
android:paddingBottom="5dp">
<TextView
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:singleLine="true"
android:text="TSBattery"
android:textColor="#FF323B42"
android:textSize="18sp"
android:textColor="@color/colorTextGray"
android:textSize="25sp"
android:textStyle="bold" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/title_github_icon"
style="?android:attr/selectableItemBackgroundBorderless"
android:layout_width="27dp"
android:layout_height="27dp"
android:layout_marginEnd="5dp"
android:alpha="0.85"
android:src="@mipmap/ic_github"
android:tint="@color/colorTextGray" />
</LinearLayout>
<LinearLayout
android:id="@+id/main_lin_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:background="@drawable/dark_round"
android:elevation="3dp"
android:layout_marginLeft="15dp"
android:layout_marginTop="10dp"
android:layout_marginRight="15dp"
android:layout_marginBottom="5dp"
android:background="@drawable/bg_dark_round"
android:elevation="0dp"
android:gravity="center">
<androidx.constraintlayout.utils.widget.ImageFilterView
@@ -39,14 +58,17 @@
android:layout_height="25dp"
android:layout_marginStart="25dp"
android:layout_marginEnd="5dp"
android:src="@mipmap/warn"
android:src="@mipmap/ic_warn"
android:tint="@color/white" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
android:paddingLeft="20dp"
android:paddingTop="10dp"
android:paddingRight="20dp"
android:paddingBottom="10dp">
<TextView
android:id="@+id/main_text_status"
@@ -57,34 +79,143 @@
android:textColor="@color/white"
android:textSize="18sp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
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:layout_marginBottom="3dp"
android:alpha="0.8"
android:text="当前版本:%1"
android:text="模块版本:%1"
android:textColor="@color/white"
android:textSize="13sp" />
<TextView
android:id="@+id/main_text_support"
android:id="@+id/main_text_release_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="3dp"
android:alpha="0.8"
android:text="支持 %1"
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="12sp" />
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.6"
android:text="理论在小更新内还会生效,如果失效请看下方的联系方式"
android:alpha="0.8"
android:ellipsize="end"
android:singleLine="true"
android:text="%1"
android:textColor="@color/white"
android:textSize="10sp"
tools:ignore="SmallSp" />
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="11sp"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
@@ -94,7 +225,6 @@
android:layout_marginBottom="10dp"
android:fadingEdgeLength="10dp"
android:fillViewport="true"
android:overScrollMode="never"
android:requiresFadingEdge="vertical">
<LinearLayout
@@ -108,19 +238,157 @@
android:layout_marginLeft="15dp"
android:layout_marginTop="10dp"
android:layout_marginRight="15dp"
android:background="@drawable/white_round"
android:elevation="2dp"
android:background="@drawable/bg_permotion_round"
android:elevation="0dp"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingRight="15dp">
android:orientation="horizontal"
android:padding="15dp">
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/protect_mode_switch"
<androidx.constraintlayout.utils.widget.ImageFilterView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="15dp"
android:alpha="0.85"
android:src="@mipmap/ic_about"
android:tint="@color/colorTextDark" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.6"
android:lineSpacingExtra="5dp"
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:text="启用保守模式"
android:textColor="#FF323B42"
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="10dp"
android:gravity="center|start">
<ImageView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="10dp"
android:src="@mipmap/ic_shot_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>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/quick_qq_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_weight="1"
android:background="@drawable/bg_button_round"
android:gravity="center"
android:padding="10dp"
android:singleLine="true"
android:text="QQ"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<TextView
android:id="@+id/quick_tim_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_weight="1"
android:background="@drawable/bg_button_round"
android:gravity="center"
android:padding="10dp"
android:singleLine="true"
android:text="TIM"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<TextView
android:id="@+id/quick_wechat_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/bg_button_round"
android:gravity="center"
android:padding="10dp"
android:singleLine="true"
android:text="微信"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
</LinearLayout>
</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: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.ui.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
@@ -129,8 +397,96 @@
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="此选项默认关闭,默认情况下模块将会干掉 QQ 和 TIM 自身的电源锁控制类,开启后模块将只对系统电源锁生效,如果你的 QQ 或 TIM 视频通话等设置发生了故障,可以尝试开启这个功能,开启后请重启 QQ 或 TIM。"
android:textColor="#777777"
android:text="此选项默认关闭,默认情况下模块将会干掉 QQ 自身的电源锁控制类,开启后模块将只对系统电源锁生效,如果你的 QQ 视频通话等设置发生了故障,可以尝试开启这个功能,开启后请重启 QQ。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<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)或(GMS)可以尝试关闭。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<com.fankes.tsbattery.ui.view.MaterialSwitch
android:id="@+id/qq_tim_core_service_kn_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: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.ui.view.MaterialSwitch
android:id="@+id/wechat_disable_hook_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>
@@ -140,19 +496,60 @@
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:layout_marginRight="15dp"
android:background="@drawable/white_round"
android:elevation="2dp"
android:background="@drawable/bg_permotion_round"
android:elevation="0dp"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingTop="15dp"
android:paddingRight="15dp">
<androidx.appcompat.widget.SwitchCompat
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center|start">
<ImageView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="10dp"
android:src="@mipmap/ic_bug" />
<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.ui.view.MaterialSwitch
android:id="@+id/notify_notify_tip_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:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="此功能仅支持 QQ、TIM在开启“系统通知栏显示 QQ、TIM 图标”后系统通知后方将在最后显示“TSBattery 守护中”字样以判断模块已经生效,若不喜欢,你可以随时关闭这个功能。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<com.fankes.tsbattery.ui.view.MaterialSwitch
android:id="@+id/notify_module_info_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="35dp"
android:layout_marginBottom="5dp"
android:text="提示模块运行信息"
android:textColor="#FF323B42"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<TextView
@@ -161,8 +558,8 @@
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="模块工作正常情况下不要开启,如果你想测试模块是否正常激活,可以打开此提示,开启后将会在启动 QQTIM 的时候提示运行信息。"
android:textColor="#777777"
android:text="模块工作正常情况下无需开启,如果你想测试模块是否正常激活,可以打开此提示,开启后将会在启动 QQTIM 或微信的时候提示运行信息。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
</LinearLayout>
@@ -172,19 +569,41 @@
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:layout_marginRight="15dp"
android:background="@drawable/white_round"
android:elevation="2dp"
android:background="@drawable/bg_permotion_round"
android:elevation="0dp"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingTop="15dp"
android:paddingRight="15dp">
<androidx.appcompat.widget.SwitchCompat
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center|start">
<ImageView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="10dp"
android:src="@mipmap/ic_home" />
<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.ui.view.MaterialSwitch
android:id="@+id/hide_icon_in_launcher_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="在桌面隐藏模块图标"
android:textColor="#FF323B42"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<TextView
@@ -193,8 +612,18 @@
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="隐藏模块图标后,模块不会再在桌面显示,你可以在 EdXposed、太极、LsPosed 中找到模块设置并打开。"
android:textColor="#777777"
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>
@@ -204,35 +633,32 @@
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:layout_marginRight="15dp"
android:background="@drawable/white_round"
android:elevation="2dp"
android:background="@drawable/bg_permotion_round"
android:elevation="0dp"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingRight="15dp">
android:padding="15dp"
android:paddingTop="15dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:layout_marginBottom="15dp"
android:gravity="center|start">
<androidx.constraintlayout.utils.widget.ImageFilterView
<ImageView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="5dp"
android:alpha="0.85"
android:src="@mipmap/about"
android:tint="#FF323B42" />
android:layout_marginEnd="10dp"
android:src="@mipmap/ic_help" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alpha="0.85"
android:singleLine="true"
android:text="使用帮助说明"
android:textColor="#FF323B42"
android:text="使用帮助&amp;说明"
android:textColor="@color/colorTextGray"
android:textSize="12sp" />
</LinearLayout>
@@ -241,9 +667,9 @@
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:textColor="#777777"
android:lineSpacingExtra="10dp"
android:text="Q.这个模块是做什么的?\nA.此模块的诞生来源于国内厂商毒瘤 APP 强行霸占后台耗电QQ 在 8.6.0 版本以后也只是接入了 HMS 推送,但是可笑的是开发组却并没有删除之前疯狂耗电的接收消息方法,于是这个模块就因此而诞生了。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<TextView
@@ -251,9 +677,9 @@
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="#777777"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<TextView
@@ -261,9 +687,9 @@
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:text="Q.如何使用?\nA.目前模块支持 EdXposed、LsPosed 以及太极(无极)框架,在太极和 LsPosed 的作用域中,只需勾选 QQ 和 TIM 即可,模块可以做到即插即用,激活后无需重启手机,重启 QQTIM 就可以了。"
android:textColor="#777777"
android:lineSpacingExtra="10dp"
android:text="Q.如何使用?\nA.目前模块支持 LSPosed、EdXposed 以及太极(无极)框架,在太极和 LSPosed 的作用域中,只需勾选 QQ、TIM、微信即可,模块可以做到即插即用,激活后无需重启手机,重启 QQTIM 或微信就可以了。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<TextView
@@ -271,9 +697,9 @@
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:text="Q.激活后一定可以非常省电吗?\nA.并不,模块只能减少 QQ、TIM 的耗电,但是请务必记住这一点,省电只是一个理论上的东西,实际水平由你使用的系统和硬件决定,如果你在前台疯狂使用 QQ、TIM那么照样会耗电模块只能保证后台运行和锁屏时毒瘤不会消耗过多的无用的电量仅此而已。"
android:textColor="#777777"
android:lineSpacingExtra="10dp"
android:text="Q.激活后一定可以非常省电吗?\nA.并不,模块只能减少 QQ、TIM、微信的耗电,但是请务必记住这一点,省电只是一个理论上的东西,实际水平由你使用的系统和硬件决定,如果你在前台疯狂使用 QQ、TIM那么照样会耗电模块只能保证后台运行和锁屏时毒瘤不会消耗过多的无用的电量仅此而已。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<TextView
@@ -281,59 +707,38 @@
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" />
<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.微信适配尚在实验阶段,敬请期待。"
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.如何对单独的 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="10dp"
android:text="Q.如何反馈问题?\nA.酷安关注 @星夜不荟"
android:textColor="#777777"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/link_with_project_address"
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/white_round"
android:elevation="2dp"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingRight="15dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:gravity="center|start">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="5dp"
android:alpha="0.85"
android:src="@mipmap/about"
android:tint="#FF323B42" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alpha="0.85"
android:singleLine="true"
android:text="项目地址"
android:textColor="#FF323B42"
android:textSize="12sp" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:text="本软件是免费开源项目,遵循 GPL 协议,你可以点击这里前往 Github 查看源码以及获取模块更新。\n严禁以任何形式贩卖、商用本软件否则开发者有权追究其法律责任。"
android:textColor="#777777"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
</LinearLayout>
@@ -344,30 +749,37 @@
android:layout_marginTop="15dp"
android:layout_marginRight="15dp"
android:layout_marginBottom="10dp"
android:background="@drawable/white_round"
android:elevation="2dp"
android:background="@drawable/bg_permotion_round"
android:elevation="0dp"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingRight="15dp">
<TextView
android:id="@+id/link_with_follow_me"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:gravity="center"
android:lineSpacingExtra="6dp"
android:text="恰饭时间\n酷安关注我获取我的更多应用"
android:textColor="#FF323B42"
android:text="恰饭时间\n点击前往酷安关注我,获取我的更多应用"
android:textColor="@color/colorTextGray"
android:textSize="16sp" />
<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
app:cardCornerRadius="15dp"
app:cardElevation="0dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:src="@mipmap/qr_pay"
tools:ignore="ContentDescription" />
android:src="@mipmap/bg_qr_pay" />
</androidx.cardview.widget.CardView>
<TextView
android:layout_width="match_parent"
@@ -376,9 +788,38 @@
android:gravity="center"
android:lineSpacingExtra="6dp"
android:text="开发者 酷安 @星夜不荟\n未经允许不得转载、修改复制我的劳动成果"
android:textColor="#FF323B42"
android:textColor="@color/colorTextGray"
android:textSize="16sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_marginBottom="10dp"
android:background="@drawable/bg_permotion_round"
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>

View File

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 201 KiB

View File

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorThemeBackground">#FF2D2D2D</color>
<color name="colorTextDark">#FFCFCFCF</color>
<color name="colorTextGray">#FFD3D3D3</color>
</resources>

View File

@@ -1,6 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<resources>
<!-- Base application theme. -->
<style name="Theme.TSBattery" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<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>
@@ -10,7 +10,7 @@
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</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,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorThemeBackground">#FFFFFFFF</color>
<color name="colorTextDark">#FF777777</color>
<color name="colorTextGray">#FF323B42</color>
</resources>

View File

@@ -1,6 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<resources>
<!-- Base application theme. -->
<style name="Theme.TSBattery" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<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>
@@ -10,7 +10,7 @@
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</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>

BIN
banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -1,28 +1,12 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = "1.4.20"
repositories {
google()
//noinspection JcenterRepositoryObsolete
jcenter()
}
dependencies {
//noinspection AndroidGradlePluginVersion
classpath "com.android.tools.build:gradle:4.1.1"
//noinspection DifferentKotlinGradleVersion
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
plugins {
id 'com.android.application' version '7.1.2' apply false
id 'com.android.library' version '7.1.2' apply false
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
}
allprojects {
repositories {
google()
//noinspection JcenterRepositoryObsolete
jcenter()
}
ext {
appVersionName = "3.5"
appVersionCode = 13
}
task clean(type: Delete) {

View File

@@ -1,6 +1,6 @@
#Sat Sep 04 04:05:23 CST 2021
#Mon Feb 14 23:27:58 CST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

View File

@@ -1,2 +1,18 @@
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
maven { url "https://api.xposed.info/" }
maven { url "https://s01.oss.sonatype.org/content/repositories/releases" }
mavenCentral()
}
}
rootProject.name = "TSBattery"
include ':app'