138 Commits
1.35 ... 1.76

Author SHA1 Message Date
117b37b5cf Update version to 1.76 2022-05-09 01:20:51 +08:00
967186bd00 Update misc 2022-05-09 01:16:53 +08:00
eea7b6d897 更新文案 2022-05-09 01:16:46 +08:00
dd75a13970 尝试修复通知面板透明功能在 ColorOS 12 不生效的问题 2022-05-09 01:11:09 +08:00
66ed43f5ad Fix Activity destroy non-null unregister bug 2022-05-09 00:57:37 +08:00
399cfafd29 Make SystemUIHooker singleton 2022-05-08 15:08:05 +08:00
6ae1d3e726 Update YukiHookAPI 2022-05-06 15:09:25 +08:00
384e6b657e Update version to 1.75 2022-05-05 15:17:04 +08:00
08b7feeede 优化通知面板背景透明度功能设置界面,添加实验性文案说明 2022-05-05 15:11:23 +08:00
e7fc0bbcd0 默认关闭通知面板背景透明度功能 2022-05-05 15:10:58 +08:00
1f8022fd7c 修复通知面板折叠通知背景异常问题,优化二级通知的背景 2022-05-05 15:10:46 +08:00
Fankesyooni
b404543f47 Merge pull request #16 from NextAlone/master
fix: notification summary background
2022-05-05 12:34:40 +08:00
NextAlone
ad195d93ea fix: notification summary background 2022-05-05 05:19:49 +08:00
15e7490808 Update YukiHookAPI 2022-05-04 14:03:55 +08:00
7c906cd2a0 Update YukiHookAPI 2022-05-04 10:15:55 +08:00
0da5875d64 Update YukiHookAPI 2022-05-04 09:32:30 +08:00
947ec809e7 Update version to 1.7 2022-05-04 06:49:38 +08:00
c07f3e99a1 新增通知面板背景透明度调节功能 2022-05-04 06:06:10 +08:00
ebba4d2345 Update YukiHookAPI 2022-05-04 05:22:56 +08:00
e60fc6ae7c Update YukiHookAPI 2022-05-01 12:08:02 +08:00
7688dbb5ca Update README.md/PRIVACY.md 2022-04-26 00:41:55 +08:00
e703237a6d Merge code 2022-04-25 02:47:34 +08:00
01494e8467 Update YukiHookAPI 2022-04-18 03:14:39 +08:00
e08081ae42 Update YukiHookAPI 2022-04-15 15:26:52 +08:00
758f80d1c1 Update YukiHookAPI 2022-04-15 05:22:20 +08:00
b06ecaa5e0 Update version to 1.6 2022-04-14 03:20:22 +08:00
df5090faac 更换通知刷新方案,彻底解决系统界面卡死问题 2022-04-14 03:03:27 +08:00
ffb706864a Update YukiHookAPI 2022-04-13 05:03:30 +08:00
70eadb83e8 Merge code 2022-04-11 23:08:21 +08:00
6d7d7290e2 Update YukiHookAPI 2022-04-10 03:05:13 +08:00
1191c77986 Update YukiHookAPI 2022-04-09 02:35:08 +08:00
2f4539d8f6 Update README.md 2022-04-09 01:53:45 +08:00
030a1590ae Update README.md 2022-04-05 22:45:54 +08:00
52820c0b07 加入新安装应用的通知图标优化适配通知忽略 DEBUG 版本的 APP 2022-04-05 22:03:09 +08:00
1f2b1d5046 Update version to 1.53 2022-04-04 23:22:10 +08:00
7896e4836c Update YukiHookAPI 2022-04-04 23:13:24 +08:00
d2ff1fe3ec 修复 ColorOS 11 Android 11 上的问题 2022-04-04 23:12:58 +08:00
f5722e3e5c Update version to 1.52 2022-04-04 14:49:03 +08:00
44e99cb3da 修复缓存图标过大造成系统界面停止运行的问题 2022-04-04 14:45:56 +08:00
1386833c91 Merge code 2022-04-04 12:03:58 +08:00
69af0172eb Update YukiHookAPI 2022-04-04 03:19:41 +08:00
81e66d2da2 Update README.md 2022-03-30 20:56:21 +08:00
73a9142e71 Merge code 2022-03-30 14:14:23 +08:00
5af6ffb614 Merge code 2022-03-29 23:04:16 +08:00
230b3b4e38 Merge code 2022-03-29 21:45:19 +08:00
66f309dc91 Update version to 1.51 2022-03-29 21:21:02 +08:00
d4f004894e Update YukiHookAPI 2022-03-29 21:17:09 +08:00
f2e27c2702 Update version to 1.5 2022-03-28 14:50:57 +08:00
ab7418931b Update version to 1.5 2022-03-28 14:29:21 +08:00
cfb0c06a6a Merge code 2022-03-28 13:57:32 +08:00
efabb726ba Merge code 2022-03-28 13:49:44 +08:00
40a05dd093 Merge code 2022-03-28 13:43:07 +08:00
3a1a8e3726 Merge code 2022-03-28 13:42:57 +08:00
8ad21a6d3b Merge code 2022-03-28 11:57:07 +08:00
9c6d61c685 Merge code 2022-03-28 01:25:38 +08:00
8f8c34e532 增加允许建立非 HTTPS 的连接 2022-03-28 01:25:20 +08:00
5281464e4e Update YukiHookAPI 2022-03-28 01:21:56 +08:00
d6f5695711 增加通知栏快捷磁贴打开在线规则列表功能 2022-03-26 12:41:54 +08:00
857eec5b6a Merge code 2022-03-26 02:33:25 +08:00
f85ccd92f3 Merge code 2022-03-25 23:42:31 +08:00
0c26c0a8cd Merge code 2022-03-25 21:30:36 +08:00
2619f924f3 Merge code 2022-03-25 15:44:32 +08:00
6a4e96923e 调整状态栏通知栏未适配的彩色推送图标为 APP 主题图标 2022-03-25 15:09:04 +08:00
44dee2ff30 Merge code 2022-03-25 14:53:34 +08:00
05e083f8e6 Merge code 2022-03-25 14:37:41 +08:00
618fd8af0e Merge code 2022-03-25 14:30:51 +08:00
287afae3cc Merge code 2022-03-25 14:30:33 +08:00
81c582ca5c Merge code 2022-03-25 14:27:45 +08:00
08eb981cc2 Merge code 2022-03-25 12:06:52 +08:00
9dbd0bc7a8 Merge code 2022-03-25 11:38:45 +08:00
cefe9b6f11 Merge code 2022-03-25 03:20:22 +08:00
836b4abaf1 Merge code 2022-03-25 01:57:43 +08:00
fad7a6f4cd Update YukiHookAPI 2022-03-25 01:51:10 +08:00
06fb6243ba Update YukiHookAPI 2022-03-25 01:02:33 +08:00
e962ac2ecf Merge code 2022-03-24 15:09:02 +08:00
a174aa3928 Merge code 2022-03-24 13:47:21 +08:00
513e17ba6b 增加模块激活状态判断以及模块更新提醒 2022-03-24 13:36:30 +08:00
e1cb703196 增加模块激活状态判断以及模块更新提醒 2022-03-24 04:03:07 +08:00
890923d9bb Merge code 2022-03-24 01:51:59 +08:00
0307c7c6fd Merge code 2022-03-24 01:39:13 +08:00
a61ed0cd2a Merge code 2022-03-24 01:33:19 +08:00
2dca9cd9fd Merge code 2022-03-23 15:19:20 +08:00
e7ff1b2b2d 适配系统壁纸主题色着色通知图标,加入通知图标动态刷新 2022-03-23 14:58:57 +08:00
3ae5ce1058 Merge code 2022-03-23 05:27:46 +08:00
d3ddbfd3ea Merge code 2022-03-23 03:23:49 +08:00
d0b2ea5f48 Merge code 2022-03-23 02:47:53 +08:00
fd6ac454cf 修复状态栏动态小图标被破坏的问题 2022-03-22 23:55:56 +08:00
1795addbf3 Merge code 2022-03-22 23:43:46 +08:00
3ecbe6159b Merge code 2022-03-22 21:10:14 +08:00
edcd81c9e8 Merge code 2022-03-22 21:08:06 +08:00
55e7256dbb Merge code 2022-03-22 21:07:35 +08:00
c3c249a37d Merge code 2022-03-22 20:34:04 +08:00
803e09b26c Merge code 2022-03-22 20:27:57 +08:00
090d8e5f2b Merge code 2022-03-22 20:27:11 +08:00
751610edaa Merge code 2022-03-22 20:23:47 +08:00
c436615d1b Merge code 2022-03-22 02:59:01 +08:00
c06abdf514 Merge code 2022-03-22 02:46:20 +08:00
5a501b9846 Merge code 2022-03-22 00:24:35 +08:00
cdeb8735d6 Merge code 2022-03-21 23:19:16 +08:00
a5f7701870 Merge code 2022-03-21 15:22:56 +08:00
b893b201d2 更新模块 UI 2022-03-21 15:13:19 +08:00
a1ca97f1b0 Merge code 2022-03-21 14:03:08 +08:00
9cc75490d4 Merge code 2022-03-21 13:52:16 +08:00
11ce9eeede Merge code 2022-03-21 11:38:42 +08:00
6bd3a5a78a 添加新应用安装后自动提醒适配通知图标功能 2022-03-21 03:33:07 +08:00
ee4da89104 增加 RealmeUI 的判断 2022-03-20 22:51:02 +08:00
f15e48d4a3 Update README.md 2022-03-20 22:33:11 +08:00
360c293c18 Update README.md 2022-03-20 22:24:12 +08:00
0e1f615abd Update README.md 2022-03-20 22:23:30 +08:00
65bd3ee58e Update README.md 2022-03-20 22:20:07 +08:00
7779f5dbe6 增加模块自动检查更新功能 2022-03-20 14:05:43 +08:00
a0fdf942e6 增加模块自动检查更新功能 2022-03-20 14:01:39 +08:00
8bc07e7dfb Merge code 2022-03-20 12:09:03 +08:00
48a18a1069 Merge code 2022-03-20 11:02:26 +08:00
6eb7569b76 Update YukiHookAPI 2022-03-20 03:29:18 +08:00
1834803a5c Merge code 2022-03-20 01:11:41 +08:00
d3053a725b Merge code 2022-03-19 01:39:28 +08:00
89945962c2 Update YukiHookAPI 2022-03-18 23:54:30 +08:00
7988fa2535 Update YukiHookAPI 2022-03-18 14:50:31 +08:00
fda47d7b53 Merge code 2022-03-18 14:47:43 +08:00
1def8ef6f2 Update YukiHookAPI 2022-03-18 14:14:31 +08:00
0ed7c5655b Update YukiHookAPI 2022-03-18 05:56:40 +08:00
7300f8e7ee 增加提醒在线规则更新功能 2022-03-18 00:30:02 +08:00
f09d422e59 Update README.md 2022-03-17 23:57:38 +08:00
490a70e8ec Update README.md 2022-03-17 23:55:30 +08:00
953c726110 Merge code 2022-03-17 05:42:47 +08:00
a90278c1bc PreBuild version 2022-03-17 04:47:51 +08:00
a0bf7b2ac7 Fix a bug 2022-03-17 04:36:28 +08:00
321273cc77 调整了一个按钮样式 2022-03-17 04:10:14 +08:00
3c1a4798d0 Update version to 1.36,fix more bugs. 2022-03-13 02:06:36 +08:00
9faab60aa8 加入多版本适配支持 2022-03-13 02:02:09 +08:00
74eb55ab39 增加移除免打扰通知功能 2022-03-13 01:59:47 +08:00
f7e75931ed Merge code 2022-03-13 00:30:25 +08:00
856c1c5cab Merge code 2022-03-13 00:30:00 +08:00
fcbf3b0565 Merge code 2022-03-13 00:28:30 +08:00
fa3cddd75f Merge code 2022-03-13 00:23:09 +08:00
d11dfe5167 Merge remote-tracking branch 'origin/master' 2022-03-13 00:22:13 +08:00
b3be2789a9 Merge code 2022-03-13 00:21:55 +08:00
56 changed files with 4370 additions and 1916 deletions

2
.idea/gradle.xml generated
View File

@@ -7,13 +7,13 @@
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="Embedded JDK" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>

10
.idea/misc.xml generated
View File

@@ -3,9 +3,15 @@
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="app/src/main/res/drawable-night/bg_orange_round.xml" value="0.232" />
<entry key="app/src/main/res/drawable-v24/ic_launcher_foreground.xml" value="0.254" />
<entry key="app/src/main/res/drawable/bg_orange_round.xml" value="0.232" />
<entry key="app/src/main/res/drawable/ic_nf_icon_refresh.xml" value="0.232" />
<entry key="app/src/main/res/drawable/ic_nf_icon_update.xml" value="0.232" />
<entry key="app/src/main/res/drawable/ic_notify_icon.xml" value="0.232" />
<entry key="app/src/main/res/drawable/ic_system_clock.xml" value="0.232" />
<entry key="app/src/main/res/layout/activity_config.xml" value="0.4375" />
<entry key="app/src/main/res/layout/activity_main.xml" value="0.335" />
<entry key="app/src/main/res/layout/activity_main.xml" value="0.31170825335892516" />
<entry key="app/src/main/res/layout/adapter_config.xml" value="0.4375" />
<entry key="app/src/main/res/layout/dia_icon_filter.xml" value="0.4375" />
<entry key="app/src/main/res/layout/dia_source_from.xml" value="0.4375" />
@@ -13,7 +19,7 @@
</map>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
<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">

359
PRIVACY.md Normal file
View File

@@ -0,0 +1,359 @@
### ColorOS 通知图标增强 隐私政策
> 简体中文
版本1.0
修订与生效日期2022年4月25日
#### 前言
为帮助您使用 ColorOS 通知图标增强产品(以下简称“本产品”)或服务,我们可能收集与提供服务相关的设备信息或申请设备权限,您有权拒绝或撤回授权;
我们采取了互联网业内标准的技术措施和数据安全措施来保护您的设备信息安全;
除非再次征得您的同意,我们不会将您的设备信息用于本政策未载明的其他目的;
在阅读本协议前,请确认您已年满 18 岁,否则请在父母的陪同下阅读本协议。
#### 目录
```
1. 引言
2. 本产品处理设备信息的法律依据
3. 我们如何收集和使用您的设备信息
3.1 向您提供本产品和/或服务的核心业务功能
3.2 征得授权同意的例外
4. 我们如何转让、公开披露您的设备信息
4.1 转让您的设备信息
4.2 公开披露
4.3 共享、转让、公开披露设备信息授权同意的例外
5. 您管理设备信息的权利
5.1 设备权限调用
6. 如何更新与修改本政策
7. 争议解决
8. 名词解释
```
1. 引言
【特别提示】
请您在使用我们的各项产品和/或服务前,仔细阅读并充分理解本政策。一旦您使用或继续使用本产品/服务,即表示您同意我们按照本政策处理您的相关信息。
2. 本产品处理设备信息的法律依据
如果您是中华人民共和国大陆地区的用户,我们将依据《中华人民共和国网络安全法》、《信息安全技术 个人信息安全规范》GB/T 35273-2017以及其他相关法律法规收集和使用您的设备信息或申请设备权限为您提供本产品或服务。 我们通常只会在征得您同意的情况下收集您的设备信息。
3. 我们如何收集和使用您的设备信息
我们会遵循正当、合法、必要的原则,出于本政策所述的以下目的,收集和使用您在使用服务过程中主动提供或因使用本产品和/或服务而产生的设备信息。如果我们要将您的设备信息用于本政策未载明的其它用途,或基于特定目的将收集而来的信息用于其他目的,我们将以合理的方式向您告知,并在使用前再次征得您的同意。
3.1 向您提供本产品和/或服务的核心业务功能
为实现 本产品的核心业务功能,我们可能需要向您收集设备信息。以下将详细列出 本产品的核心业务功能及为实现该功能所需收集的设备信息,若您拒绝收集,则无法使用该服务。
查看设备中的所有应用信息。 本产品会读取您的设备应用列表;
搜索功能。 当您使用 本产品提供的搜索功能时,我们可能会使用应用数据缓存,收集您设备上的信息并进行本地存储。 该信息通常无法单独识别您的个人身份。
3.2 征得授权同意的例外
根据相关法律法规的规定,在以下情形中,我们可以在不征得您的授权同意的情况下收集、使用一些必要的设备信息:
```
a. 与国家安全、国防安全直接相关的;
b. 与公共安全、公共卫生、重大公共利益直接相关的;
c. 与犯罪侦查、起诉、审判和判决执行等直接相关的;
d. 出于维护您或其他个人的生命、财产等重大合法权益但又很难得到本人同意的;
e. 所收集的设备信息是您自行向社会公众公开的;
f. 从合法公开披露的信息中收集到您的设备信息,如从合法的新闻报道、政府信息公开等渠道;
g. 根据您的要求签订和履行合同所必需的;
h. 用于维护 本产品的产品和/或服务的安全稳定运行所必需的,例如发现、处置产品或服务的故障;
i. 法律法规规定的其他情形。
```
4. 我们如何共享、转让、公开披露您的设备信息
4.1 转让您的设备信息
除非获取您的明确同意,我们不会将您的设备信息转让给任何公司、组织或个人。
4.2 公开披露
除非获取您的明确同意,我们不会公开披露您的设备信息。
4.3 共享、转让、公开披露设备信息授权同意的例外
根据相关法律法规的规定,在以下情形中,我们可以在不征得您的授权同意的情况下共享、转让、公开披露您的设备信息:
```
A. 与国家安全、国防安全有关的;
B. 与公共安全、公共卫生、重大公共利益有关的;
C. 与犯罪侦查、起诉、审判和判决执行等有关的;
D. 出于维护您或其他个人的生命、财产等重大合法权益但又很难得到本人同意的;
E. 您自行向社会公众公开的设备信息;
F. 从合法公开披露的信息中收集到的设备信息的,如合法的新闻报道、政府信息公开等渠道。
G. 法律法规规定的其他情形。
```
根据法律规定,共享、转让经去标识化处理的设备信息,且确保数据接收方无法复原并重新识别设备信息主体的,不属于设备信息的对外共享、转让及公开披露行为,对此类数据的保存及处理将无需另行向您通知并征得您的同意。
5. 您管理设备信息的权利
5.1 设备权限调用
我们在提供服务的过程中,可能需要您开通一些设备权限,例如网络连接、查看设备应用列表等访问权限。 您也可以在设备的【设置】功能中随时选择关闭部分或者全部权限,从而拒绝我们收集您相应的设备信息。在不同设备中,权限显示方式及关闭方式可能有所不同,具体请参考设备及系统开发方说明或指引。
6. 如何更新与修改本政策
6.1 本政策为本产品的重要组成部分。 本产品保留不时更新或修改本政策的权利。
6.2 未经您明确同意,我们不会削减您按照本政策所应享有的权利。我们会通过 App 客户端推送通知、弹窗形式等合理方式通知您,以便您能及时了解本政策所做的任何变更。
6.3 对于重大变更,视具体情况我们 可能还会提供更为显著的通知 说明本政策的具体变更内容。 重大变更包括但不限于:
```
A. 我们的服务模式发生重大变化。如处理设备信息的目的、处理的设备信息类型、设备信息的使用方式等;
B. 我们在所有权结构、组织架构等方面发生重大变化。如业务调整、破产并购等引起的所有者变更等;
C. 设备信息共享、转让或公开披露的主要对象发生变化;
D. 您参与设备信息处理方面的权利及其行使方式发生重大变化;
E. 我们负责处理设备信息安全的责任部门、联络方式及投诉渠道发生变化;
F. 设备信息安全影响评估报告表明存在高风险时。
```
6.4 若您不同意修改后的隐私政策,您有权并应立即停止使用 本产品的服务。如果您继续使用 本产品的服务,则视为您接受 本产品对本政策相关条款所做的修改。
7. 争议解决
7.1 如果您认为我们的设备信息处理行为损害了您的合法权益,您也可向有关政府部门进行反映。
8. 名词解释
本隐私政策中使用到的名词,在通常意义中有如下定义:
设备信息: 设备信息是指以电子或者其他方式记录的能够单独或者与其他信息结合识别设备身份的各种信息,包括但不限于设备的品牌、型号、系统版本、 唯一设备标识符、 网络 IP 地址、Cookie等
设备: 设备是指可用于访问本产品和/或服务的装置,例如台式计算机、笔记本电脑、平板电脑或智能手机。 唯一设备标识符: 唯一设备标识符(专属 ID 或 UUID是指由设备制造商编入到设备中的一串字符可用于以独有方式标识相应设备例如手机的 IMEI
号)。唯一设备标识符有多种用途,其中可在不能使用 Cookie例如在移动应用程序中时用以提供广告。
IP 地址: 每台上网的设备都会指定一个编号,称为互联网协议 ( IP ) 地址。这些编号通常都是根据地理区域指定的。IP 地址通常可用于识别设备连接至互联网时所在的位置。
信息收集技术: 我们在《ColorOS 通知图标增强 隐私政策》中所述的“自动收集”包括以下方式: A. Cookie曲奇档案 Cookie 是您浏览网页时,网站服务器放在客户端(您的计算机、移动电话或其他智能终端内)里面的一个小小的文本文件,当您再次访问相应网站时,网站就可通过
Cookie 识别您的浏览器。Cookie 可能会存储用户偏好及其他信息。您可以将浏览器配置为拒绝所有 Cookie 或在网站发送 Cookie 时显示提示。不过,如果没有 Cookie某些网站功能或服务可能无法正常工作。 B. Web beacon网络信标 Web beacon
是装嵌在网站或电邮内的电子图像文件案或其他技术,可用于计算访客数目、记录您是否及何时阅览电邮或网站,或用以使用某些 Cookie。 C. Log files日志文件 Log files 储存自动收集的若干数据。该等数据包括互联网协议( IP )地址、浏览器类型、互联网服务提供商(
ISP )、引用/退出页面、操作系统、日期/时间戳和点击流数据等。 D. ET Tag实体标签 ET Tag 是在互联网浏览器与互联网服务器之间背后传送的 HTTP 协议标头,可代替 Cookie用以追踪个别使用者使我们可更深入地了解和改善我们的服务。 E. JavaScript
JavaScript 是一种编程语言用于制作更具互动性和动态的网页。JavaScript 可以设定 Cookie、阅读 Cookie 及删除 Cookie。
算法: 计算机在执行解题运算时遵循的流程或一系列规则。
应用数据缓存: 应用数据缓存是指设备上的一种数据存储机制。使用它有很多好处,例如,可让网络应用在未连接互联网的情况下运行,以及可通过提高内容加载速度来改善相关应用的性能。
非个人身份信息: 记录的与用户相关的信息,但实际上不可直接或间接识别您身份的信息,包括经过去标识化、匿名化处理或化名方式提供的设备信息。
去标识化: 指通过对设备信息的技术处理,使其在不借助额外信息的情况下,无法识别设备信息主体的过程。
匿名化: 指通过对设备信息的技术处理,使得设备信息主体无法被识别,且处理后的信息不能被复原的过程。
服务器日志: 与大多数网站一样,我们的服务器会自动记录您在访问网站时所发出的网页请求。这些“服务器日志”通常包括您的网络请求、互联网协议地址、浏览器类型、浏览器语言、请求的日期和时间及可以唯一识别您的浏览器的一个或多个 Cookie。
### ColorOSNotifyIcon Privacy Policy
> English
Version: 1.0
Amendment and effective date: April 25, 2022
#### Preface
In order to help you use ColorOSNotifyIcon products (hereinafter referred to as "this product") or services, we may collect device information
related to the provision of services or apply for device permissions, and you have the right to refuse or withdraw authorization;
We have adopted technical measures and data security measures that are standard in the Internet industry to protect the security of your device
information;
Unless we obtain your consent again, we will not use your device information for other purposes not specified in this policy;
Before reading this agreement, please confirm that you are at least 18 years old, otherwise please read this agreement with your parents.
#### content
````
1 Introduction
2. Legal basis for this product to process device information
3. How we collect and use your device information
3.1 Provide you with the core business functions of this product and/or service
3.2 Exceptions to Authorized Consent
4. How we transfer and publicly disclose your device information
4.1 Transfer your device information
4.2 Public disclosure
4.3 Exceptions to Authorization and Consent for Sharing, Transfer, and Public Disclosure of Device Information
5. Your Right to Manage Device Information
5.1 Device permission call
6. How to update and modify this policy
7. Dispute Resolution
8. Glossary
````
1 Introduction
【Special Note】
Please read and fully understand this policy before using our products and/or services. Once you use or continue to use this product/service, you
agree that we will process your relevant information in accordance with this policy.
2. Legal basis for this product to process device information
If you are a user in the mainland of the People's Republic of China, we will collect and use your Device information or apply for device
permissions to provide you with this product or service. We generally only collect your device information with your consent.
3. How we collect and use your device information
We will follow the principles of legitimacy, lawfulness, and necessity, and for the following purposes described in this policy, to collect and
use the device information that you actively provide during the use of the service or that arises from the use of this product and/or service. If
we want to use your device information for other purposes not specified in this policy, or use the collected information for other purposes based
on a specific purpose, we will inform you in a reasonable manner and ask again before use with your consent.
3.1 Provide you with the core business functions of this product and/or service
In order to implement the core business functions of this product, we may need to collect device information from you. The following will list in
detail the core business functions of this product and the device information that needs to be collected to achieve this function. If you refuse
to collect, you cannot use the service.
View all app information on the device. This product will read your device application list;
searching feature. When you use the search function provided by this product, we may use application data cache, collect information on your
device and store it locally. This information generally does not individually identify you as an individual.
3.2 Exceptions to Authorized Consent
According to relevant laws and regulations, we may collect and use some necessary device information without your authorization and consent under
the following circumstances:
````
a. Directly related to national security and national defense security;
b. Directly related to public safety, public health, and major public interests;
c. Directly related to criminal investigation, prosecution, trial and execution of judgments;
d. In order to protect your or other personal life, property and other major legitimate rights and interests, but it is difficult to obtain my consent;
e. The collected device information is disclosed to the public by you;
f. Your device information is collected from legally publicly disclosed information, such as legal news reports, government information disclosure and other channels;
g. Necessary to enter into and perform a contract at your request;
h. Necessary to maintain the safe and stable operation of the products and/or services of this product, such as finding and disposing of faults in the products or services;
i. Other situations stipulated by laws and regulations.
````
4. How we share, transfer, and publicly disclose your device information
4.1 Transfer your device information
We will not transfer your device information to any company, organization or individual without your express consent.
4.2 Public disclosure
We will not publicly disclose your device information without your express consent.
4.3 Exceptions to Authorization and Consent for Sharing, Transfer, and Public Disclosure of Device Information
According to relevant laws and regulations, we may share, transfer and publicly disclose your device information without your authorization and
consent in the following circumstances:
````
A. Related to national security and national defense security;
B. Related to public safety, public health, and major public interests;
C. Related to criminal investigation, prosecution, trial and execution of judgments;
D. In order to protect your or other personal life, property and other major legitimate rights and interests, but it is difficult to obtain my consent;
E. The device information that you disclose to the public by yourself;
F. Device information collected from legally publicly disclosed information, such as legal news reports, government information disclosure and other channels.
G. Other situations stipulated by laws and regulations.
````
According to the law, sharing and transferring de-identified device information and ensuring that the data recipient cannot restore and
re-identify the subject of the device information is not an act of external sharing, transfer and public disclosure of device information.
Storage and processing will not be required to notify you and obtain your consent.
5. Your Right to Manage Device Information
5.1 Device permission call
In the process of providing services, we may require you to activate some device permissions, such as network connection, viewing device
application list and other access permissions. You can also choose to turn off some or all permissions at any time in the [Settings] function of
the device, thereby refusing us to collect your corresponding device information. In different devices, the display method and closing method of
permissions may be different. For details, please refer to the instructions or guidelines of the device and system developers.
6. How to update and modify this policy
6.1 This policy is an important part of this product. This product reserves the right to update or modify this policy from time to time.
6.2 We will not reduce your rights under this Policy without your express consent. We will notify you through reasonable means such as app client
push notifications and pop-up windows, so that you can keep abreast of any changes to this policy.
6.3 For major changes, we may also provide a more prominent notice to explain the specific changes to this policy as the case may be. Significant
changes include, but are not limited to:
````
A. Significant changes to our service model. Such as the purpose of processing device information, the type of device information processed, the way of using device information, etc.;
B. We have made significant changes in our ownership structure, organizational structure, etc. Such as changes in owners caused by business adjustments, bankruptcy mergers and acquisitions, etc.;
C. The main object of equipment information sharing, transfer or public disclosure has changed;
D. Significant changes in your rights to participate in the processing of device information and the way you exercise it;
E. The responsible department, contact information and complaint channel that we are responsible for handling equipment information security have changed;
F. When the equipment information security impact assessment report indicates that there is a high risk.
````
6.4 If you do not agree with the revised Privacy Policy, you have the right and should immediately stop using the services of this product. If
you continue to use the services of this product, it is deemed that you accept the modifications made by this product to the relevant terms of
this policy.
7. Dispute Resolution
7.1 If you believe that our device information processing has damaged your legitimate rights and interests, you can also report to the relevant
government departments.
8. Glossary
Terms used in this Privacy Policy have the following definitions in their usual meanings:
Device information: Device information refers to various information recorded electronically or in other ways that can identify the identity of
the device alone or in combination with other information, including but not limited to the brand, model, system version, unique device
identifier, network IP address of the device , Cookies, etc.;
Device: A device is a device that can be used to access the Product and/or Service, such as a desktop computer, laptop, tablet or smartphone.
Unique Device Identifier: A Unique Device Identifier (Unique ID or UUID) is a string of characters programmed into a device by the device
manufacturer that can be used to uniquely identify the device (such as the IMEI of a mobile phone)
No). Unique device identifiers are used for a variety of purposes, including serving advertisements when cookies cannot be used, such as in
mobile applications.
IP Address: Every device that goes online is assigned a number called an Internet Protocol (IP) address. These numbers are usually assigned by
geographic area. An IP address can often be used to identify where a device is connected to the Internet.
Information collection technology: The "automatic collection" described in the "ColorOSNotifyIcon Privacy Policy" includes the following methods:
A. Cookie (cookie file) Cookie is when you browse the web, the website server places it on the client (your A small text file in a computer,
mobile phone or other intelligent terminal), when you visit the corresponding website again, the website can pass Cookies identify your browser.
Cookies may store user preferences and other information. You can configure your browser to refuse all cookies or to display a prompt when a
website sends a cookie. However, some website features or services may not function properly without cookies. B. Web beacon (web beacon)
Web beacon An electronic image file or other technology embedded in a website or email that can be used to count visitors, record whether and
when you view an email or website, or to use certain cookies. C. Log files Log files store certain data collected automatically. This data
includes Internet Protocol (IP) address, browser type, Internet Service Provider (
ISP), referring/exit pages, operating system, date/time stamp and clickstream data, etc. D. ET Tag (Entity Tag) ET Tag is an HTTP protocol header
transmitted behind the Internet browser and Internet server, which can replace cookies to track individual users, so that we can better
understand and improve our Serve. E. JavaScript JavaScript is a programming language used to make more interactive and dynamic web pages.
JavaScript can set cookies, read cookies and delete cookies.
Algorithm: A process or set of rules that a computer follows when performing a problem-solving operation.
App data cache: App data cache refers to a data storage mechanism on the device. There are many benefits to using it, such as allowing web
applications to run without an internet connection, and improving the performance of related applications by increasing the speed of content
loading.
Non-Personally Identifiable Information: Information that is recorded about a user but does not actually identify you directly or indirectly,
including device information that has been de-identified, anonymized, or provided under a pseudonym.
De-identification: refers to the process of technical processing of device information to make it impossible to identify the subject of device
information without the aid of additional information.
Anonymization: refers to the process in which the subject of the device information cannot be identified and the processed information cannot be
recovered through the technical processing of the device information.
Server Logs: Like most websites, our servers automatically log the web page requests you make while visiting the website. These "server logs"
typically include your web request, internet protocol address, browser type, browser language, date and time of the request, and one or more
cookies that uniquely identify your browser.

View File

@@ -1,44 +1,60 @@
# ColorOS 通知图标增强
![Eclipse Marketplace](https://img.shields.io/badge/build-passing-brightgreen)
![Eclipse Marketplace](https://img.shields.io/badge/license-AGPL3.0-blue)
![Eclipse Marketplace](https://img.shields.io/badge/version-v1.35-green)
[![Blank](https://img.shields.io/badge/build-passing-brightgreen)](https://github.com/fankes/ColorOSNotifyIcon)
[![Blank](https://img.shields.io/badge/license-AGPL3.0-blue)](https://github.com/fankes/ColorOSNotifyIcon/blob/master/LICENSE)
[![Blank](https://img.shields.io/badge/version-v1.76-green)](https://github.com/fankes/ColorOSNotifyIcon/releases)
[![Blank](https://img.shields.io/github/downloads/fankes/ColorOSNotifyIcon/total?label=Release)](https://github.com/fankes/ColorOSNotifyIcon/releases)
[![Blank](https://img.shields.io/github/downloads/Xposed-Modules-Repo/com.fankes.coloros.notify/total?label=LSPosed%20Repo&logo=Android&style=flat&labelColor=F48FB1&logoColor=ffffff)](https://github.com/Xposed-Modules-Repo/com.fankes.coloros.notify/releases)
[![Telegram](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram)](https://t.me/XiaofangInternet)
<br/><br/>
<img src="https://github.com/fankes/ColorOSNotifyIcon/blob/master/app/src/main/ic_launcher-playstore.png" width = "100" height = "100"/>
<br/>
Optimize notification icons for ColorOS and adapt to native notification icon specifications.<br/>
Optimize notification icons for ColorOS and adapt to native notification icon specifications.
为 ColorOS 优化通知图标以及适配原生通知图标规范,理论支持 OxygenOS 和 RealmeUI。
# 开始使用
## Developer
点击下载最新版本
<a href='https://github.com/fankes/ColorOSNotifyIcon/releases'>![Eclipse Marketplace](https://img.shields.io/badge/download-v1.35-green)</a>
<br/><br/>
⚠️ 适配说明<br/>
[酷安 @星夜不荟](http://www.coolapk.com/u/876977)
## 适配说明
- 此模块仅支持 <b>LSPosed</b>(作用域“系统界面”)、<b>~~EdXposed(随时停止支持)~~</b>、不支持<b>太极、无极</b>
- 此模块仅支持 LSPosed(作用域“系统界面”)、~~EdXposed(随时停止支持)~~、不支持太极无极
- 目前仅在 ColorOS 12 for OnePlus 上测试通过,如有问题请提交 `issues`
- 建议在不低于 ColorOS 11 的版本上使用
# 请勿用于非法用途
## 请勿用于非法用途
- 本模块完全开源免费,如果好用你可以打赏支持开发,但是请不要用于非法用途。
- 本模块发布地址仅有 [Xposed-Modules-Repo](https://github.com/Xposed-Modules-Repo/com.fankes.coloros.notify/releases)、
[Release](https://github.com/fankes/ColorOSNotifyIcon/releases)
及[蓝奏云](https://fankes.lanzouy.com/b030rvjyf),从其他非正规渠道下载到的版本或对您造成任何影响均与我们无关。
[Release](https://github.com/fankes/ColorOSNotifyIcon/releases)、[蓝奏云](https://fankes.lanzouy.com/b030rvjyf) 及**酷安应用市场**
,从其他非正规渠道下载到的版本或对您造成任何影响均与我们无关。
# 贡献通知图标优化名单
## 贡献通知图标优化名单
此项目是 `AndroidNotifyIconAdapt` 项目的一部分,详情请参考下方。<br/>
此项目是 `AndroidNotifyIconAdapt` 项目的一部分,详情请参考下方。
- [Android 通知图标规范适配计划](https://github.com/fankes/AndroidNotifyIconAdapt)
# 历史背景
## 历史背景
继 MIUI 之后的第二大系统 ColorOS 虽然支持原生通知图标,但是第三方推送五颜六色的图标系统并没有做适配,甚至系统自己的图标都是彩色的,极其不友好。
继 MIUI 之后的第二大系统 ColorOS 虽然支持原生通知图标,但是第三方推送五颜六色的图标系统并没有做适配,甚至系统自己的图标都是彩色的,极其不友好。<br/>
而且从 ColorOS 12 开始,原生图标丢失了着色属性,这也是一种对原生 Android 生态的破坏。
# 许可证
## 捐赠支持
- 工作不易,无意外情况此项目将继续维护下去,提供更多可能,欢迎打赏。<br/><br/>
<img src="https://github.com/fankes/YuKiHookAPI/blob/master/img-src/wechat_code.jpg" width = "200" height = "200"/>
## 隐私政策
- [PRIVACY](https://github.com/fankes/ColorOSNotifyIcon/blob/master/PRIVACY.md)
## 许可证
- [AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.html)
@@ -59,5 +75,6 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
```
Powered by [YukiHookAPI](https://github.com/fankes/YukiHookAPI)<br/><br/>
Powered by [YukiHookAPI](https://github.com/fankes/YukiHookAPI)
版权所有 © 2019-2022 Fankes Studio(qzmmcn@163.com)

View File

@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'com.google.devtools.ksp' version '1.6.10-1.0.2'
id 'com.google.devtools.ksp' version '1.6.21-1.0.5'
}
android {
@@ -30,17 +30,25 @@ android {
buildTypes {
release {
minifyEnabled true
minifyEnabled rootProject.ext.enableR8
signingConfig signingConfigs.debug
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = '1.8'
jvmTarget = '11'
freeCompilerArgs = [
'-Xno-param-assertions',
'-Xno-call-assertions',
'-Xno-receiver-assertions'
]
}
buildFeatures {
viewBinding true
}
}
@@ -61,9 +69,8 @@ tasks.whenTaskAdded {
dependencies {
compileOnly 'de.robv.android.xposed:api:82'
implementation 'com.highcapable.yukihookapi:api:1.0.4'
ksp 'com.highcapable.yukihookapi:ksp-xposed:1.0.4'
implementation 'com.github.tiann:FreeReflection:3.1.0'
implementation 'com.highcapable.yukihookapi:api:1.0.86'
ksp 'com.highcapable.yukihookapi:ksp-xposed:1.0.86'
implementation "com.github.topjohnwu.libsu:core:3.1.2"
implementation 'androidx.annotation:annotation:1.3.0'
implementation 'com.geyifeng.immersionbar:immersionbar:3.2.0'
@@ -71,7 +78,7 @@ dependencies {
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
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 'com.google.android.material:material:1.6.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'

View File

@@ -20,31 +20,25 @@
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-dontwarn
-ignorewarnings
-optimizationpasses 10
-dontusemixedcaseclassnames
-dontoptimize
-verbose
-overloadaggressively
-repackageclasses o
-allowaccessmodification
-adaptclassstrings
-adaptresourcefilenames
-adaptresourcefilecontents
#-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
-renamesourcefileattribute H
-renamesourcefileattribute P
-keepattributes SourceFile,LineNumberTable
-keep class android.support**
-keep class androidx**
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
public static *** throwUninitializedProperty(...);
public static *** throwUninitializedPropertyAccessException(...);
}
-keep class me.weishu**{*;}
-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
-keepclassmembers class * implements androidx.viewbinding.ViewBinding {
*** inflate(android.view.LayoutInflater);
}

View File

@@ -1,8 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.fankes.coloros.notify">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:name=".application.CNNApplication"
@@ -11,7 +13,9 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ColorOSNotifyIcon">
android:theme="@style/Theme.ColorOSNotifyIcon"
android:usesCleartextTraffic="true"
tools:ignore="AllowBackup,ExportedService">
<meta-data
android:name="xposedmodule"
@@ -22,11 +26,15 @@
<meta-data
android:name="xposedminversion"
android:value="93" />
<meta-data
android:name="xposedscope"
android:resource="@array/module_scope" />
<activity
android:name=".ui.MainActivity"
android:name=".ui.activity.MainActivity"
android:exported="true"
android:screenOrientation="behind">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -39,7 +47,8 @@
android:exported="true"
android:label="@string/app_name"
android:screenOrientation="behind"
android:targetActivity=".ui.MainActivity">
android:targetActivity=".ui.activity.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -48,8 +57,26 @@
</activity-alias>
<activity
android:name=".ui.ConfigureActivity"
android:exported="false"
android:name=".ui.activity.ConfigureActivity"
android:exported="true"
android:screenOrientation="behind" />
<activity
android:name=".ui.activity.auto.NotifyIconRuleUpdateActivity"
android:excludeFromRecents="true"
android:exported="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
<service
android:name=".service.QuickStartTileService"
android:exported="true"
android:icon="@drawable/ic_notify_icon"
android:label="@string/tile_name"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
</application>
</manifest>

View File

@@ -24,32 +24,13 @@
package com.fankes.coloros.notify.application
import android.app.Application
import android.content.Context
import androidx.appcompat.app.AppCompatDelegate
import me.weishu.reflection.Reflection
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication
class CNNApplication : Application() {
companion object {
/** 全局静态实例 */
private var context: CNNApplication? = null
/** 调用全局静态实例 */
val appContext get() = context ?: error("App is death")
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
/** 解锁隐藏 API */
Reflection.unseal(base)
}
class CNNApplication : ModuleApplication() {
override fun onCreate() {
super.onCreate()
/** 设置静态实例 */
context = this
/** 跟随系统夜间模式 */
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}

View File

@@ -23,7 +23,7 @@
package com.fankes.coloros.notify.bean
import android.graphics.Bitmap
import com.fankes.coloros.notify.utils.base64
import com.fankes.coloros.notify.utils.factory.base64
import java.io.Serializable
/**

View File

@@ -0,0 +1,60 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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/24.
*/
@file:Suppress("MemberVisibilityCanBePrivate")
package com.fankes.coloros.notify.const
import com.fankes.coloros.notify.BuildConfig
/**
* 存储一些静态编译后的值
*/
object Const {
/** 当前模块的包名 */
const val MODULE_PACKAGE_NAME = BuildConfig.APPLICATION_ID
/** 当前模块的版本名称 */
const val MODULE_VERSION_NAME = BuildConfig.VERSION_NAME
/** 当前模块的版本号 */
const val MODULE_VERSION_CODE = BuildConfig.VERSION_CODE
/** 当前模块的版本校验 */
const val MODULE_VERSION_VERIFY = "${MODULE_VERSION_NAME}_${MODULE_VERSION_CODE}_202205090117"
/** 当前模块的版本校验标签 */
const val MODULE_VERSION_VERIFY_TAG = "module_version_verify"
/** 发送通讯广播号标签 - 模块激活状态 */
const val ACTION_MODULE_CHECKING_RECEIVER = "cnn_module_checking_action"
/** 接收通讯广播号标签 - 模块激活状态 */
const val ACTION_MODULE_HANDLER_RECEIVER = "cnn_module_handler_action"
/** 发送通讯广播号标签 - 通知系统界面刷新 */
const val ACTION_REMIND_CHECKING_RECEIVER = "cnn_remind_checking_action"
/** 接收通讯广播号标签 - 通知系统界面刷新 */
const val ACTION_REMIND_HANDLER_RECEIVER = "cnn_remind_handler_action"
}

View File

@@ -0,0 +1,48 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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/28.
*/
package com.fankes.coloros.notify.data
import com.fankes.coloros.notify.hook.HookConst
import com.fankes.coloros.notify.utils.factory.isUpperOfAndroidS
import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
object DataConst {
val ENABLE_MODULE = PrefsData("_enable_module", true)
val ENABLE_MODULE_LOG = PrefsData("_enable_module_log", false)
val ENABLE_HIDE_ICON = PrefsData("_hide_icon", false)
val ENABLE_ANDROID12_STYLE = PrefsData("_notify_android12_style", isUpperOfAndroidS)
val ENABLE_NOTIFY_ICON_FIX = PrefsData("_notify_icon_fix", true)
val ENABLE_NOTIFY_ICON_FIX_NOTIFY = PrefsData("_notify_icon_fix_notify", true)
val REMOVE_DEV_NOTIFY = PrefsData("_remove_dev_notify", true)
val REMOVE_CHANGECP_NOTIFY = PrefsData("_remove_charge_complete_notify", false)
val REMOVE_DNDALERT_NOTIFY = PrefsData("_remove_dndalert_notify", false)
val ENABLE_NOTIFY_ICON_FIX_AUTO = PrefsData("_enable_notify_icon_fix_auto", true)
val ENABLE_NOTIFY_PANEL_ALPHA = PrefsData("_enable_notify_panel_alpha", false)
val NOTIFY_PANEL_ALPHA = PrefsData("_notify_panel_alpha", 185)
val NOTIFY_ICON_DATAS = PrefsData("_notify_icon_datas", "")
val NOTIFY_ICON_FIX_AUTO_TIME = PrefsData("_notify_icon_fix_auto_time", "07:00")
val SOURCE_SYNC_WAY = PrefsData("_rule_source_sync_way", HookConst.TYPE_SOURCE_SYNC_WAY_1)
val SOURCE_SYNC_WAY_CUSTOM_URL = PrefsData("_rule_source_sync_way_custom_url", "")
}

View File

@@ -24,21 +24,10 @@ package com.fankes.coloros.notify.hook
object HookConst {
const val ENABLE_MODULE = "_enable_module"
const val ENABLE_MODULE_LOG = "_enable_module_log"
const val ENABLE_HIDE_ICON = "_hide_icon"
const val ENABLE_ANDROID12_STYLE = "_notify_android12_style"
const val ENABLE_NOTIFY_ICON_FIX = "_notify_icon_fix"
const val REMOVE_DEV_NOTIFY = "_remove_dev_notify"
const val REMOVE_CHANGECP_NOTIFY = "_remove_charge_complete_notify"
const val NOTIFY_ICON_DATAS = "_notify_icon_datas"
const val SOURCE_SYNC_WAY = "_source_sync_way"
const val SOURCE_SYNC_WAY_CUSTOM_URL = "_source_sync_way_custom_url"
const val TYPE_SOURCE_SYNC_WAY_1 = 1000
const val TYPE_SOURCE_SYNC_WAY_2 = 2000
const val TYPE_SOURCE_SYNC_WAY_3 = 3000
const val ANDROID_PACKAGE_NAME = "android"
const val SYSTEMUI_PACKAGE_NAME = "com.android.systemui"
}

View File

@@ -22,376 +22,32 @@
*/
package com.fankes.coloros.notify.hook
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.graphics.drawable.VectorDrawable
import android.service.notification.StatusBarNotification
import android.widget.ImageView
import androidx.core.graphics.drawable.toBitmap
import com.fankes.coloros.notify.bean.IconDataBean
import com.fankes.coloros.notify.hook.HookConst.ENABLE_ANDROID12_STYLE
import com.fankes.coloros.notify.hook.HookConst.ENABLE_MODULE
import com.fankes.coloros.notify.hook.HookConst.ENABLE_MODULE_LOG
import com.fankes.coloros.notify.hook.HookConst.ENABLE_NOTIFY_ICON_FIX
import com.fankes.coloros.notify.hook.HookConst.REMOVE_CHANGECP_NOTIFY
import com.fankes.coloros.notify.hook.HookConst.REMOVE_DEV_NOTIFY
import com.fankes.coloros.notify.data.DataConst
import com.fankes.coloros.notify.hook.HookConst.SYSTEMUI_PACKAGE_NAME
import com.fankes.coloros.notify.hook.factory.isAppNotifyHookAllOf
import com.fankes.coloros.notify.hook.factory.isAppNotifyHookOf
import com.fankes.coloros.notify.param.IconPackParams
import com.fankes.coloros.notify.utils.*
import com.fankes.coloros.notify.utils.drawable.drawabletoolbox.DrawableBuilder
import com.fankes.coloros.notify.hook.entity.SystemUIHooker
import com.fankes.coloros.notify.utils.factory.isNotColorOS
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.factory.method
import com.highcapable.yukihookapi.hook.log.loggerD
import com.highcapable.yukihookapi.hook.log.loggerW
import com.highcapable.yukihookapi.hook.param.PackageParam
import com.highcapable.yukihookapi.hook.type.android.ContextClass
import com.highcapable.yukihookapi.hook.type.android.DrawableClass
import com.highcapable.yukihookapi.hook.type.android.IconClass
import com.highcapable.yukihookapi.hook.type.android.ImageViewClass
import com.highcapable.yukihookapi.hook.type.java.BooleanType
import com.highcapable.yukihookapi.hook.type.java.IntType
import com.highcapable.yukihookapi.hook.xposed.proxy.YukiHookXposedInitProxy
import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit
@InjectYukiHookWithXposed
class HookEntry : YukiHookXposedInitProxy {
class HookEntry : IYukiHookXposedInit {
companion object {
/** 原生存在的类 */
private const val ContrastColorUtilClass = "com.android.internal.util.ContrastColorUtil"
/** 原生存在的类 */
private const val NotificationUtilsClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationUtils"
/** 原生存在的类 */
private const val NotificationEntryClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.collection.NotificationEntry"
/** 原生存在的类 */
private const val StatusBarIconClass = "com.android.internal.statusbar.StatusBarIcon"
/** 原生存在的类 */
private const val IconBuilderClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.icon.IconBuilder"
/** 原生存在的类 */
private const val IconManagerClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.icon.IconManager"
/** ColorOS 存在的类 - 旧版本不存在 */
private const val OplusContrastColorUtilClass = "com.oplusos.util.OplusContrastColorUtil"
/** ColorOS 存在的类 */
private const val SystemPromptControllerClass = "com.oplusos.systemui.statusbar.policy.SystemPromptController"
/** 根据多个版本存在不同的包名相同的类 */
private val OplusPowerNotificationWarningsClass = VariousClass(
"com.oplusos.systemui.notification.power.OplusPowerNotificationWarnings",
"com.coloros.systemui.notification.power.ColorosPowerNotificationWarnings"
)
/** 根据多个版本存在不同的包名相同的类 */
private val ExpandableNotificationRowClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.ExpandableNotificationRow",
"$SYSTEMUI_PACKAGE_NAME.statusbar.ExpandableNotificationRow"
)
/** 根据多个版本存在不同的包名相同的类 */
private val NotificationViewWrapperClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.wrapper.NotificationViewWrapper",
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationViewWrapper"
)
/** 根据多个版本存在不同的包名相同的类 */
private val NotificationHeaderViewWrapperClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper",
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationHeaderViewWrapper"
)
}
/** 缓存的通知优化图标数组 */
private var iconDatas = ArrayList<IconDataBean>()
/**
* 获取推送通知的应用名称
* @param packageName APP 包名
* @return [String]
*/
private fun Context.findAppName(packageName: String) = safeOf(default = "<unknown>") {
packageManager.getPackageInfo(packageName, 0).applicationInfo.loadLabel(packageManager)
}
/**
* 打印日志
* @param tag 标识
* @param context 实例
* @param packageName APP 包名
* @param isCustom 是否为通知优化生效图标
* @param isGrayscale 是否为灰度图标
*/
private fun PackageParam.printLogcat(
tag: String,
context: Context,
packageName: String,
isCustom: Boolean,
isGrayscale: Boolean
) {
if (prefs.getBoolean(ENABLE_MODULE_LOG)) loggerD(
msg = "$tag --> [${context.findAppName(packageName)}][$packageName] " +
"custom [$isCustom] " +
"grayscale [$isGrayscale]"
)
}
/**
* - 这个是修复彩色图标的关键核心代码判断
*
* 判断是否为灰度图标 - 反射执行系统方法
* @param context 实例
* @param drawable 要判断的图标
* @return [Boolean]
*/
private fun PackageParam.isGrayscaleIcon(context: Context?, drawable: Drawable?) =
ContrastColorUtilClass.clazz.let {
drawable is VectorDrawable || it.method {
name = "isGrayscaleIcon"
param(DrawableClass)
}.get(it.method {
name = "getInstance"
param(ContextClass)
}.get().invoke(context)).invoke<Boolean>(drawable) ?: false
}
/**
* 自动适配状态栏、通知栏自定义小图标
* @param isGrayscaleIcon 是否为灰度图标
* @param packageName APP 包名
* @return [Pair] - ([Bitmap] 位图,[Int] 颜色)
*/
private fun PackageParam.compatCustomIcon(isGrayscaleIcon: Boolean, packageName: String): Pair<Bitmap?, Int> {
var customPair: Pair<Bitmap?, Int>? = null
when {
/** 替换系统图标为 Android 默认 */
(packageName == "android" || packageName == "com.android.systemui") && !isGrayscaleIcon -> customPair =
Pair(if (isUpperOfAndroidS) IconPackParams.android12IconBitmap else IconPackParams.android11IconBitmap, 0)
/** 替换自定义通知图标 */
prefs.getBoolean(ENABLE_NOTIFY_ICON_FIX, default = true) -> run {
if (iconDatas.isNotEmpty())
iconDatas.forEach {
if (packageName == it.packageName && isAppNotifyHookOf(it)) {
if (!isGrayscaleIcon || isAppNotifyHookAllOf(it))
customPair = Pair(it.iconBitmap, it.iconColor)
return@run
}
}
}
}
return customPair ?: Pair(null, 0)
}
/**
* 自动适配状态栏小图标
* @param context 实例
* @param isGrayscaleIcon 是否为灰度图标
* @param packageName APP 包名
* @param drawable 原始图标
* @return [Bitmap]
*/
private fun PackageParam.compatStatusIcon(context: Context, isGrayscaleIcon: Boolean, packageName: String, drawable: Drawable) =
compatCustomIcon(isGrayscaleIcon, packageName).first.also {
/** 打印日志 */
printLogcat(tag = "StatusIcon", context, packageName, isCustom = it != null, isGrayscaleIcon)
} ?: drawable.toBitmap()
/**
* 自动适配通知栏小图标
* @param isGrayscaleIcon 是否为灰度图标
* @param packageName APP 包名
* @param drawable 原始图标
* @param iconColor 原生图标颜色
* @param iconView 图标 [ImageView]
*/
private fun PackageParam.compatNotifyIcon(
isGrayscaleIcon: Boolean,
packageName: String,
drawable: Drawable,
iconColor: Int,
iconView: ImageView
) {
compatCustomIcon(isGrayscaleIcon, packageName).also { customPair ->
when {
customPair.first != null || isGrayscaleIcon -> iconView.apply {
setImageBitmap(customPair.first ?: drawable.toBitmap())
/** 是否开启 Android 12 风格 */
val isA12Style = prefs.getBoolean(ENABLE_ANDROID12_STYLE, isUpperOfAndroidS)
/** 旧版风格 */
val oldStyle = (if (context.isSystemInDarkMode) 0xffdcdcdc else 0xff707173).toInt()
/** 新版风格 */
val newStyle = (if (context.isSystemInDarkMode) 0xffdcdcdc else Color.WHITE).toInt()
/** 优化风格 */
val fixStyle = (if (context.isSystemInDarkMode) 0xff707173 else oldStyle).toInt()
/** 旧版图标着色 */
val oldApplyColor = customPair.second.takeIf { it != 0 } ?: iconColor.takeIf { it != 0 } ?: oldStyle
/** 新版图标着色 */
val newApplyColor = customPair.second.takeIf { it != 0 } ?: iconColor.takeIf { it != 0 } ?: fixStyle
/** 判断风格并开始 Hook */
if (isA12Style) {
background = DrawableBuilder().rounded().solidColor(newApplyColor).build()
setColorFilter(newStyle)
setPadding(2.dp(context), 2.dp(context), 2.dp(context), 2.dp(context))
} else {
background = null
setColorFilter(oldApplyColor)
setPadding(0, 0, 0, 0)
}
}
else -> iconView.apply {
setPadding(0, 0, 0, 0)
background = null
colorFilter = null
}
}
/** 打印日志 */
printLogcat(tag = "NotifyIcon", iconView.context, packageName, isCustom = customPair.first != null, isGrayscaleIcon)
}
}
override fun onHook() {
runConfig()
runHook()
}
/** 配置 Hook */
private fun runConfig() = configs {
override fun onInit() = configs {
debugTag = "ColorOSNotify"
isDebug = false
}
/** 开始 Hook */
private fun runHook() = encase {
loadApp(SYSTEMUI_PACKAGE_NAME) {
when {
/** 不是 ColorOS 系统停止 Hook */
isNotColorOS -> loggerW(msg = "Aborted Hook -> This System is not ColorOS")
/** Hook 被手动关闭停止 Hook */
!prefs.getBoolean(ENABLE_MODULE, default = true) -> loggerW(msg = "Aborted Hook -> Hook Closed")
/** 开始 Hook */
else -> {
/** 缓存图标数据 */
iconDatas = IconPackParams(param = this).iconDatas
/** 移除开发者警告通知 */
SystemPromptControllerClass.hook {
injectMember {
method { name = "updateDeveloperMode" }
beforeHook {
/** 是否移除 */
if (prefs.getBoolean(REMOVE_DEV_NOTIFY, default = true)) resultNull()
}
}
}
/** 移除充电完成通知 */
OplusPowerNotificationWarningsClass.hook {
injectMember {
method {
name = "showChargeErrorDialog"
param(IntType)
}
beforeHook {
/** 是否移除 */
if (firstArgs as Int == 7 && prefs.getBoolean(
REMOVE_CHANGECP_NOTIFY,
default = false
)
) resultNull()
}
}
}
/** 修复并替换新版本 ColorOS 原生灰度图标色彩判断*/
NotificationUtilsClass.hook {
injectMember {
method {
name = "isGrayscaleOplus"
param(ImageViewClass, OplusContrastColorUtilClass.clazz)
}
replaceAny { (firstArgs as? ImageView?)?.let { isGrayscaleIcon(it.context, it.drawable) } }
}.ignoredHookingFailure()
}
/** 替换状态栏图标 */
IconManagerClass.hook {
injectMember {
method {
name = "getIconDescriptor"
param(NotificationEntryClass.clazz, BooleanType)
}
afterHook {
IconBuilderClass.clazz.field { name = "context" }
.of<Context>(field { name = "iconBuilder" }.of(instance))?.also { context ->
NotificationEntryClass.clazz.method {
name = "getSbn"
}.get(firstArgs).invoke<StatusBarNotification>()?.also { nf ->
nf.notification.smallIcon.loadDrawable(context).also { iconDrawable ->
StatusBarIconClass.clazz.field {
name = "icon"
type = IconClass
}.get(result).set(
Icon.createWithBitmap(
compatStatusIcon(
context = context,
isGrayscaleIcon = isGrayscaleIcon(context, iconDrawable),
packageName = nf.packageName,
drawable = iconDrawable
)
)
)
}
}
}
}
}
}
/** 替换通知图标和样式 */
NotificationHeaderViewWrapperClass.hook {
injectMember {
method { name = "resolveHeaderViews" }
afterHook {
NotificationHeaderViewWrapperClass.clazz
.field { name = "mIcon" }.of<ImageView>(instance)?.apply {
ExpandableNotificationRowClass.clazz
.method { name = "getEntry" }
.get(NotificationViewWrapperClass.clazz.field {
name = "mRow"
}.get(instance).self).call()?.let {
it.javaClass.method {
name = "getSbn"
}.get(it).invoke<StatusBarNotification>()
}?.notification?.also { nf ->
nf.smallIcon.loadDrawable(context).also { iconDrawable ->
compatNotifyIcon(
isGrayscaleIcon = isGrayscaleIcon(context, iconDrawable),
packageName = context.packageName,
drawable = iconDrawable,
iconColor = nf.color,
iconView = this
)
}
}
}
}
}
}
}
}
override fun onHook() = encase {
when {
/** 不是 ColorOS 系统停止 Hook */
isNotColorOS -> loggerW(msg = "Aborted Hook -> This System is not ColorOS")
/** Hook 被手动关闭停止 Hook */
prefs.get(DataConst.ENABLE_MODULE).not() -> loggerW(msg = "Aborted Hook -> Hook Closed")
/** 开始 Hook */
else -> loadApp(SYSTEMUI_PACKAGE_NAME, SystemUIHooker)
}
}
}

View File

@@ -0,0 +1,846 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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/25.
*/
@file:Suppress("StaticFieldLeak")
package com.fankes.coloros.notify.hook.entity
import android.app.WallpaperManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.Outline
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.graphics.drawable.VectorDrawable
import android.service.notification.StatusBarNotification
import android.util.ArrayMap
import android.view.View
import android.view.ViewGroup
import android.view.ViewOutlineProvider
import android.widget.ImageView
import androidx.core.graphics.drawable.toBitmap
import androidx.core.view.children
import com.fankes.coloros.notify.bean.IconDataBean
import com.fankes.coloros.notify.const.Const
import com.fankes.coloros.notify.data.DataConst
import com.fankes.coloros.notify.hook.HookConst.ANDROID_PACKAGE_NAME
import com.fankes.coloros.notify.hook.HookConst.SYSTEMUI_PACKAGE_NAME
import com.fankes.coloros.notify.hook.factory.isAppNotifyHookAllOf
import com.fankes.coloros.notify.hook.factory.isAppNotifyHookOf
import com.fankes.coloros.notify.param.IconPackParams
import com.fankes.coloros.notify.utils.drawable.drawabletoolbox.DrawableBuilder
import com.fankes.coloros.notify.utils.factory.*
import com.fankes.coloros.notify.utils.tool.IconAdaptationTool
import com.highcapable.yukihookapi.hook.bean.VariousClass
import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
import com.highcapable.yukihookapi.hook.factory.current
import com.highcapable.yukihookapi.hook.factory.field
import com.highcapable.yukihookapi.hook.factory.hasMethod
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.log.loggerD
import com.highcapable.yukihookapi.hook.log.loggerE
import com.highcapable.yukihookapi.hook.log.loggerW
import com.highcapable.yukihookapi.hook.type.android.*
import com.highcapable.yukihookapi.hook.type.java.BooleanType
import com.highcapable.yukihookapi.hook.type.java.FloatType
import com.highcapable.yukihookapi.hook.type.java.IntType
import com.highcapable.yukihookapi.hook.type.java.LongType
/**
* 系统界面核心 Hook 类
*/
object SystemUIHooker : YukiBaseHooker() {
/** 原生存在的类 */
private const val ContrastColorUtilClass = "com.android.internal.util.ContrastColorUtil"
/** 原生存在的类 */
private const val NotificationUtilsClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationUtils"
/** 原生存在的类 */
private const val NotificationEntryClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.collection.NotificationEntry"
/** 原生存在的类 */
private const val StatusBarIconClass = "com.android.internal.statusbar.StatusBarIcon"
/** 原生存在的类 */
private const val StatusBarIconViewClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.StatusBarIconView"
/** 原生存在的类 */
private const val IconBuilderClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.icon.IconBuilder"
/** 原生存在的类 */
private const val IconManagerClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.icon.IconManager"
/** ColorOS 存在的类 - 旧版本不存在 */
private const val OplusContrastColorUtilClass = "com.oplusos.util.OplusContrastColorUtil"
/** 原生存在的类 */
private const val PluginManagerImplClass = "$SYSTEMUI_PACKAGE_NAME.shared.plugins.PluginManagerImpl"
/** 原生存在的类 */
private const val NotificationBackgroundViewClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.NotificationBackgroundView"
/** ColorOS 存在的类 - 旧版本不存在 */
private const val OplusNotificationBackgroundViewClass =
"com.oplusos.systemui.statusbar.notification.row.OplusNotificationBackgroundView"
/** 根据多个版本存在不同的包名相同的类 */
private val OplusNotificationIconAreaControllerClass = VariousClass(
"com.oplusos.systemui.statusbar.phone.OplusNotificationIconAreaController",
"com.oplusos.systemui.statusbar.policy.OplusNotificationIconAreaController",
"com.coloros.systemui.statusbar.policy.ColorNotificationIconAreaController"
)
/** 根据多个版本存在不同的包名相同的类 */
private val SystemPromptControllerClass = VariousClass(
"com.oplusos.systemui.statusbar.policy.SystemPromptController",
"com.coloros.systemui.statusbar.policy.ColorSystemPromptController"
)
/** 根据多个版本存在不同的包名相同的类 */
private val RoundRectDrawableUtilClass = VariousClass(
"com.oplusos.systemui.notification.util.RoundRectDrawableUtil",
"com.coloros.systemui.notification.util.RoundRectDrawableUtil"
)
/** 根据多个版本存在不同的包名相同的类 */
private val RoundRectDrawableUtil_CompanionClass = VariousClass(
"com.oplusos.systemui.notification.util.RoundRectDrawableUtil\$Companion",
"com.oplusos.systemui.notification.util.RoundRectDrawableUtil\$Companion"
)
/** 根据多个版本存在不同的包名相同的类 */
private val DndAlertHelperClass = VariousClass(
"com.oplusos.systemui.notification.helper.DndAlertHelper",
"com.coloros.systemui.notification.helper.DndAlertHelper"
)
/** 根据多个版本存在不同的包名相同的类 */
private val OplusPowerNotificationWarningsClass = VariousClass(
"com.oplusos.systemui.notification.power.OplusPowerNotificationWarnings",
"com.coloros.systemui.notification.power.ColorosPowerNotificationWarnings"
)
/** 根据多个版本存在不同的包名相同的类 */
private val AbstractReceiverClass = VariousClass(
"com.oplusos.systemui.common.receiver.AbstractReceiver",
"com.coloros.systemui.common.receiver.AbstractReceiver"
)
/** 根据多个版本存在不同的包名相同的类 */
private val StatusBarNotificationPresenterClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.phone.StatusBarNotificationPresenter",
"$SYSTEMUI_PACKAGE_NAME.statusbar.phone.StatusBar"
)
/** 根据多个版本存在不同的包名相同的类 */
private val ExpandableNotificationRowClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.ExpandableNotificationRow",
"$SYSTEMUI_PACKAGE_NAME.statusbar.ExpandableNotificationRow"
)
/** 根据多个版本存在不同的包名相同的类 */
private val NotificationViewWrapperClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.wrapper.NotificationViewWrapper",
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationViewWrapper"
)
/** 根据多个版本存在不同的包名相同的类 */
private val NotificationHeaderViewWrapperClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper",
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationHeaderViewWrapper"
)
/** 缓存的彩色 APP 图标 */
private var appIcons = ArrayMap<String, Drawable>()
/** 缓存的通知优化图标数组 */
private var iconDatas = ArrayList<IconDataBean>()
/** 状态栏通知图标容器 */
private var notificationIconContainer: ViewGroup? = null
/** 状态栏通知图标数组 */
private var notificationIconInstances = ArrayList<View>()
/** 通知栏通知控制器 */
private var notificationPresenter: Any? = null
/** 仅监听一次主题壁纸颜色变化 */
private var isWallpaperColorListenerSetUp = false
/** 是否已经使用过缓存刷新功能 */
private var isUsingCachingMethod = false
/** 是否已经注册广播 */
private var isRegisterReceiver = false
/** 用户解锁屏幕广播接收器 */
private val userPresentReceiver by lazy {
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
/** 解锁后重新刷新状态栏图标防止系统重新设置它 */
if (isUsingCachingMethod) refreshStatusBarIcons()
}
}
}
/** 模块广播接收器 */
private val moduleCheckingReceiver by lazy {
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
context?.sendBroadcast(Intent().apply {
action = Const.ACTION_MODULE_HANDLER_RECEIVER
putExtra("isRegular", true)
putExtra("isValied", intent?.isValiedModule)
})
}
}
}
/** 通知广播接收器 */
private val remindCheckingReceiver by lazy {
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) = delayedRun(ms = 300) {
if (intent?.isValiedModule == true)
recachingPrefs(intent.getBooleanExtra("isRefreshCacheOnly", false))
context?.sendBroadcast(Intent().apply {
action = Const.ACTION_REMIND_HANDLER_RECEIVER
putExtra("isGrasp", true)
putExtra("isValied", intent?.isValiedModule)
})
}
}
}
/**
* 判断模块和宿主版本是否一致
* @return [Boolean]
*/
private val Intent.isValiedModule get() = getStringExtra(Const.MODULE_VERSION_VERIFY_TAG) == Const.MODULE_VERSION_VERIFY
/**
* 注册广播接收器
* @param context 实例
*/
private fun registerReceiver(context: Context) {
if (isRegisterReceiver) return
context.registerReceiver(userPresentReceiver, IntentFilter().apply { addAction(Intent.ACTION_USER_PRESENT) })
context.registerReceiver(moduleCheckingReceiver, IntentFilter().apply { addAction(Const.ACTION_MODULE_CHECKING_RECEIVER) })
context.registerReceiver(remindCheckingReceiver, IntentFilter().apply { addAction(Const.ACTION_REMIND_CHECKING_RECEIVER) })
isRegisterReceiver = true
}
/**
* 是否启用通知图标优化功能
* @param isHooking 是否判断启用通知功能 - 默认:是
* @return [Boolean]
*/
private fun isEnableHookColorNotifyIcon(isHooking: Boolean = true) =
prefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX) && (if (isHooking) prefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX_NOTIFY) else true)
/**
* 判断通知是否来自系统推送
* @return [Boolean]
*/
private val StatusBarNotification.isOplusPush get() = opPkg == ANDROID_PACKAGE_NAME && opPkg != packageName
/**
* 判断通知背景是否为旧版本
* @return [Boolean]
*/
private val isOldNotificationBackground
get() = safeOfFalse {
NotificationBackgroundViewClass.clazz.hasMethod {
name = "drawCustom"
paramCount = 2
}
}
/**
* 打印日志
* @param tag 标识
* @param context 实例
* @param packageName APP 包名
* @param isCustom 是否为通知优化生效图标
* @param isGrayscale 是否为灰度图标
*/
private fun printLogcat(
tag: String,
context: Context,
packageName: String,
isCustom: Boolean,
isGrayscale: Boolean
) {
if (prefs.get(DataConst.ENABLE_MODULE_LOG)) loggerD(
msg = "$tag --> [${context.findAppName(packageName)}][$packageName] " +
"custom [$isCustom] " +
"grayscale [$isGrayscale]"
)
}
/**
* 注册主题壁纸改变颜色监听
*
* - 仅限在 Android 12 上注册
* @param view 实例
*/
private fun registerWallpaperColorChanged(view: View) = runInSafe {
if (isWallpaperColorListenerSetUp.not() && isUpperOfAndroidS) view.apply {
WallpaperManager.getInstance(context).addOnColorsChangedListener({ _, _ -> refreshNotificationIcons() }, handler)
}
isWallpaperColorListenerSetUp = true
}
/** 刷新状态栏小图标 */
private fun refreshStatusBarIcons() = runInSafe {
val nfField = StatusBarIconViewClass.clazz.field { name = "mNotification" }
val sRadiusField = StatusBarIconViewClass.clazz.field { name = "sIconRadiusFraction" }
val sNfSizeField = StatusBarIconViewClass.clazz.field { name = "sNotificationRoundIconSize" }
val roundUtil = RoundRectDrawableUtil_CompanionClass.clazz.method {
name = "getRoundRectDrawable"
param(DrawableClass, FloatType, IntType, IntType, ContextClass)
}.onNoSuchMethod { loggerE(msg = "Your system not support \"getRoundRectDrawable\"!", e = it) }
.get(RoundRectDrawableUtilClass.clazz.field { name = "Companion" }.get().self)
/** 启动一个线程防止卡顿 */
Thread {
(notificationIconContainer?.children?.toList() ?: notificationIconInstances.takeIf { it.isNotEmpty() })?.forEach {
runInSafe {
/** 得到通知实例 */
val nf = nfField.get(it).cast<StatusBarNotification>() ?: return@Thread
/** 得到原始通知图标 */
val iconDrawable = nf.notification.smallIcon.loadDrawable(it.context)
/** 获取优化后的状态栏通知图标 */
compatStatusIcon(
context = it.context,
nf = nf,
isGrayscaleIcon = isGrayscaleIcon(it.context, iconDrawable),
packageName = nf.packageName,
drawable = iconDrawable
).also { pair ->
/** 得到图标圆角 */
val sRadius = sRadiusField.get(it).float()
/** 得到缩放大小 */
val sNfSize = sNfSizeField.get(it).int()
/** 在主线程设置图标 */
it.post { (it as? ImageView?)?.setImageDrawable(roundUtil.invoke(pair.first, sRadius, sNfSize, sNfSize, it.context)) }
}
}
}
}.start()
}
/** 刷新通知小图标 */
private fun refreshNotificationIcons() = runInSafe {
notificationPresenter?.current {
method {
name = "updateNotificationsOnDensityOrFontScaleChanged"
emptyParam()
}.call()
}
}
/**
* - 这个是修复彩色图标的关键核心代码判断
*
* 判断是否为灰度图标 - 反射执行系统方法
* @param context 实例
* @param drawable 要判断的图标
* @return [Boolean]
*/
private fun isGrayscaleIcon(context: Context?, drawable: Drawable?) =
ContrastColorUtilClass.clazz.let {
drawable is VectorDrawable || it.method {
name = "isGrayscaleIcon"
param(DrawableClass)
}.get(it.method {
name = "getInstance"
param(ContextClass)
}.get().invoke(context)).boolean(drawable)
}
/**
* 适配通知栏、状态栏来自系统推送的彩色 APP 图标
*
* 适配第三方图标包对系统包管理器更换图标后的彩色图标
* @param iconDrawable 原始图标
* @return [Drawable] 适配的图标
*/
private fun StatusBarNotification.compatPushingIcon(iconDrawable: Drawable) = safeOf(iconDrawable) {
/** 给系统推送设置 APP 自己的图标 */
if (isOplusPush && opPkg.isNotBlank())
appIcons[packageName] ?: iconDrawable
else iconDrawable
}
/**
* 自动适配状态栏、通知栏自定义小图标
* @param isGrayscaleIcon 是否为灰度图标
* @param packageName APP 包名
* @return [Pair] - ([Bitmap] 位图,[Int] 颜色)
*/
private fun compatCustomIcon(isGrayscaleIcon: Boolean, packageName: String): Pair<Bitmap?, Int> {
var customPair: Pair<Bitmap?, Int>? = null
when {
/** 替换系统图标为 Android 默认 */
(packageName == ANDROID_PACKAGE_NAME || packageName == SYSTEMUI_PACKAGE_NAME) && isGrayscaleIcon.not() ->
customPair = Pair(if (isUpperOfAndroidS) IconPackParams.android12IconBitmap else IconPackParams.android11IconBitmap, 0)
/** 替换自定义通知图标 */
prefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX) -> run {
iconDatas.takeIf { it.isNotEmpty() }?.forEach {
if (packageName == it.packageName && isAppNotifyHookOf(it)) {
if (isGrayscaleIcon.not() || isAppNotifyHookAllOf(it))
customPair = Pair(it.iconBitmap, it.iconColor)
return@run
}
}
}
}
return customPair ?: Pair(null, 0)
}
/**
* 自动适配状态栏小图标
* @param context 实例
* @param nf 通知实例
* @param isGrayscaleIcon 是否为灰度图标
* @param packageName APP 包名
* @param drawable 原始图标
* @return [Pair] - ([Drawable] 图标,[Boolean] 是否替换)
*/
private fun compatStatusIcon(
context: Context,
nf: StatusBarNotification,
isGrayscaleIcon: Boolean,
packageName: String,
drawable: Drawable
) = compatCustomIcon(isGrayscaleIcon, packageName).first.also {
/** 打印日志 */
printLogcat(tag = "StatusIcon", context, packageName, isCustom = it != null, isGrayscaleIcon)
}?.let { Pair(BitmapDrawable(context.resources, it), true) }
?: Pair(if (isGrayscaleIcon) drawable else nf.compatPushingIcon(drawable), isGrayscaleIcon.not())
/**
* 自动适配通知栏小图标
* @param context 实例
* @param nf 通知实例
* @param isGrayscaleIcon 是否为灰度图标
* @param packageName APP 包名
* @param drawable 原始图标
* @param iconColor 原生图标颜色
* @param iconView 图标 [ImageView]
*/
private fun compatNotifyIcon(
context: Context,
nf: StatusBarNotification,
isGrayscaleIcon: Boolean,
packageName: String,
drawable: Drawable,
iconColor: Int,
iconView: ImageView
) = runInSafe {
compatCustomIcon(isGrayscaleIcon, packageName).also { customPair ->
when {
customPair.first != null || isGrayscaleIcon -> iconView.apply {
/** 设置不要裁切到边界 */
clipToOutline = false
/** 重新设置图标 */
setImageBitmap(customPair.first ?: drawable.toBitmap())
/** 是否开启 Android 12 风格 */
val isA12Style = prefs.get(DataConst.ENABLE_ANDROID12_STYLE)
/** 旧版风格 */
val oldStyle = (if (context.isSystemInDarkMode) 0xffdcdcdc else 0xff707173).toInt()
/** 新版风格 */
val newStyle = (if (context.isSystemInDarkMode) 0xffdcdcdc else Color.WHITE).toInt()
/** 原生着色 */
val a12Style = if (isUpperOfAndroidS) context.wallpaperColor else
(if (context.isSystemInDarkMode) 0xff707173 else oldStyle).toInt()
/** 旧版图标着色 */
val oldApplyColor = customPair.second.takeIf { it != 0 } ?: iconColor.takeIf { it != 0 } ?: oldStyle
/** 新版图标着色 */
val newApplyColor = customPair.second.takeIf { it != 0 } ?: iconColor.takeIf { it != 0 } ?: a12Style
/** 判断风格并开始 Hook */
if (isA12Style) {
background = DrawableBuilder().rounded().solidColor(newApplyColor).build()
setColorFilter(newStyle)
setPadding(2.dp(context), 2.dp(context), 2.dp(context), 2.dp(context))
} else {
background = null
setColorFilter(oldApplyColor)
setPadding(0, 0, 0, 0)
}
}
else -> iconView.apply {
/** 重新设置图标 */
setImageDrawable(nf.compatPushingIcon(drawable))
/** 设置裁切到边界 */
clipToOutline = true
/** 设置一个圆角轮廓裁切 */
outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View, out: Outline) {
out.setRoundRect(
0, 0,
view.width, view.height, 3.dpFloat(context)
)
}
}
/** 清除图标间距 */
setPadding(0, 0, 0, 0)
/** 清除背景 */
background = null
/** 清除着色 */
colorFilter = null
}
}
/** 打印日志 */
printLogcat(tag = "NotifyIcon", iconView.context, packageName, isCustom = customPair.first != null, isGrayscaleIcon)
}
}
/**
* 设置通知面板背景透明度
* @param view 背景 View 实例
* @param drawable 背景实例
*/
private fun modifyNotifyPanelAlpha(view: View?, drawable: Drawable?) {
prefs.get(DataConst.ENABLE_NOTIFY_PANEL_ALPHA).also { isEnabled ->
/** 设置通知面板背景透明度 */
when {
isEnabled.not() -> drawable?.alpha = 255
view?.parent?.parent?.javaClass?.name?.contains(other = "ChildrenContainer") == true -> drawable?.alpha = 0
else -> drawable?.alpha = prefs.get(DataConst.NOTIFY_PANEL_ALPHA)
}
/** 移除阴影效果 */
if (isEnabled) view?.elevation = 0f
}
}
/** 缓存图标数据 */
private fun cachingIconDatas() {
iconDatas.clear()
IconPackParams(param = this).iconDatas.apply {
when {
isNotEmpty() -> forEach { iconDatas.add(it) }
isEmpty() && isEnableHookColorNotifyIcon(isHooking = false) -> loggerW(msg = "NotifyIconSupportData is empty!")
}
}
}
/**
* 刷新缓存数据
* @param isRefreshCacheOnly 仅刷新缓存不刷新图标和通知改变 - 默认:否
*/
private fun recachingPrefs(isRefreshCacheOnly: Boolean = false) {
isUsingCachingMethod = true
prefs.clearCache()
cachingIconDatas()
if (isRefreshCacheOnly) return
refreshStatusBarIcons()
refreshNotificationIcons()
}
override fun onHook() {
/** 缓存图标数据 */
cachingIconDatas()
/** 移除开发者警告通知 */
SystemPromptControllerClass.hook {
injectMember {
method { name = "updateDeveloperMode" }
beforeHook {
/** 是否移除 */
if (prefs.get(DataConst.REMOVE_DEV_NOTIFY)) resultNull()
}
}
}
/** 移除充电完成通知 */
OplusPowerNotificationWarningsClass.hook {
injectMember {
method {
name = "showChargeErrorDialog"
param(IntType)
}
beforeHook {
/** 是否移除 */
if (args().first().int() == 7 && prefs.get(DataConst.REMOVE_CHANGECP_NOTIFY)) resultNull()
}
}
}
/** 移除免打扰通知 */
DndAlertHelperClass.hook {
injectMember {
method {
name = "sendNotificationWithEndtime"
param(LongType)
}
beforeHook {
/** 是否移除 */
if (prefs.get(DataConst.REMOVE_DNDALERT_NOTIFY)) resultNull()
}
}
}
/** 修复并替换新版本 ColorOS 原生灰度图标色彩判断 */
NotificationUtilsClass.hook {
injectMember {
method {
name = "isGrayscaleOplus"
param(ImageViewClass, OplusContrastColorUtilClass)
}
replaceAny { args().first().cast<ImageView>()?.let { isGrayscaleIcon(it.context, it.drawable) } }
}.ignoredHookingFailure()
}
/** 替换状态栏图标 */
IconManagerClass.hook {
injectMember {
method {
name = "getIconDescriptor"
param(NotificationEntryClass, BooleanType)
}
afterHook {
IconBuilderClass.clazz.field { name = "context" }
.get(field { name = "iconBuilder" }.get(instance).cast()).cast<Context>()?.also { context ->
NotificationEntryClass.clazz.method {
name = "getSbn"
}.get(args().first().any()).invoke<StatusBarNotification>()?.also { nf ->
nf.notification.smallIcon.loadDrawable(context).also { iconDrawable ->
compatStatusIcon(
context = context,
nf = nf,
isGrayscaleIcon = isGrayscaleIcon(context, iconDrawable).also {
/** 缓存第一次的 APP 小图标 */
if (it.not()) context.findAppIcon(nf.packageName)
?.also { e -> appIcons[nf.packageName] = e }
},
packageName = nf.packageName,
drawable = iconDrawable
).also { pair ->
if (pair.second) StatusBarIconClass.clazz.field {
name = "icon"
type = IconClass
}.get(result).set(Icon.createWithBitmap(pair.first.toBitmap()))
}
}
}
}
}
}
}
/** 得到状态栏图标实例 */
StatusBarIconViewClass.hook {
injectMember {
method {
name = "setNotification"
param(StatusBarNotificationClass)
}
afterHook {
if (args().first().any() != null) instance<ImageView>().also {
/** 注册壁纸颜色监听 */
registerWallpaperColorChanged(it)
/** 注册广播 */
registerReceiver(it.context)
}
}
}
}
/** 注入通知控制器实例 */
StatusBarNotificationPresenterClass.hook {
injectMember {
allConstructors()
afterHook { notificationPresenter = instance }
}
}
/** 注入状态栏通知图标容器实例 */
OplusNotificationIconAreaControllerClass.hook {
injectMember {
var isOldWay = false
method {
name = "updateIconsForLayout"
paramCount = 10
}.remedys {
method {
name = "updateIconsForLayout"
paramCount = 1
}.onFind { isOldWay = true }
}
afterHook {
if (isOldWay) {
notificationIconInstances.clear()
field { name = "mLastToShow" }.get(instance).list<View>()
.takeIf { it.isNotEmpty() }?.forEach { notificationIconInstances.add(it) }
} else notificationIconContainer = args(index = 1).cast()
}
}
}
/** 替换通知面板背景 - 新版本 */
OplusNotificationBackgroundViewClass.hook {
injectMember {
method {
name = "drawRegionBlur"
paramCount = 2
}.remedys {
method {
name = "draw"
paramCount = 2
}
}
beforeHook { modifyNotifyPanelAlpha(instance(), args().last().cast<Drawable>()) }
}
injectMember {
method {
name = "draw"
paramCount = 2
superClass(isOnlySuperClass = true)
}
beforeHook { modifyNotifyPanelAlpha(instance(), args().last().cast<Drawable>()) }
}
}.by { isOldNotificationBackground.not() }
/** 替换通知面板背景 - 旧版本 */
NotificationBackgroundViewClass.hook {
injectMember {
method {
name = "draw"
paramCount = 2
}
beforeHook { modifyNotifyPanelAlpha(instance(), args().last().cast<Drawable>()) }
}
injectMember {
method {
name = "drawCustom"
paramCount = 2
}
beforeHook { modifyNotifyPanelAlpha(instance(), args().last().cast<Drawable>()) }
}
}.by { isOldNotificationBackground }
/** 替换通知面板背景 - 避免折叠展开通知二次修改通知面板背景 */
ExpandableNotificationRowClass.hook {
injectMember {
method {
name = "updateBackgroundForGroupState"
emptyParam()
}
beforeHook {
if (prefs.get(DataConst.ENABLE_NOTIFY_PANEL_ALPHA))
field { name = "mShowGroupBackgroundWhenExpanded" }.get(instance).setTrue()
}
}
}
/** 替换通知图标和样式 */
NotificationHeaderViewWrapperClass.hook {
injectMember {
method { name = "resolveHeaderViews" }
afterHook {
NotificationHeaderViewWrapperClass.clazz
.field { name = "mIcon" }.get(instance).cast<ImageView>()?.apply {
ExpandableNotificationRowClass.clazz
.method { name = "getEntry" }
.get(NotificationViewWrapperClass.clazz.field {
name = "mRow"
}.get(instance).self).call()?.let {
it.javaClass.method {
name = "getSbn"
}.get(it).invoke<StatusBarNotification>()
}.also { nf ->
nf?.notification?.also {
it.smallIcon.loadDrawable(context).also { iconDrawable ->
compatNotifyIcon(
context = context,
nf = nf,
isGrayscaleIcon = isGrayscaleIcon(context, iconDrawable),
packageName = context.packageName,
drawable = iconDrawable,
iconColor = it.color,
iconView = this
)
}
}
}
}
}
}
}
/** 发送适配新的 APP 图标通知 */
PluginManagerImplClass.hook {
injectMember {
method {
name = "onReceive"
param(ContextClass, IntentClass)
}
afterHook {
if (isEnableHookColorNotifyIcon()) args().last().cast<Intent>()?.also {
if (it.action.equals(Intent.ACTION_PACKAGE_REPLACED).not() &&
it.getBooleanExtra(Intent.EXTRA_REPLACING, false)
) return@also
when (it.action) {
Intent.ACTION_PACKAGE_ADDED ->
it.data?.schemeSpecificPart?.also { newPkgName ->
if (iconDatas.takeIf { e -> e.isNotEmpty() }
?.filter { e -> e.packageName == newPkgName }
.isNullOrEmpty()
) IconAdaptationTool.pushNewAppSupportNotify(args().first().cast()!!, newPkgName)
}
Intent.ACTION_PACKAGE_REMOVED ->
IconAdaptationTool.removeNewAppSupportNotify(
context = args().first().cast()!!,
packageName = it.data?.schemeSpecificPart ?: ""
)
}
}
}
}
}
/** 自动检查通知图标优化更新的注入监听 */
AbstractReceiverClass.hook {
injectMember {
method {
name = "onReceive"
param(ContextClass, IntentClass)
}
afterHook {
args().first().cast<Context>()?.also {
/** 注册广播 */
registerReceiver(it)
/** 注册定时监听 */
if (isEnableHookColorNotifyIcon() && prefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX_AUTO))
IconAdaptationTool.prepareAutoUpdateIconRule(
context = it,
timeSet = prefs.get(DataConst.NOTIFY_ICON_FIX_AUTO_TIME)
)
}
}
}
}
}
}

View File

@@ -31,40 +31,36 @@ import com.highcapable.yukihookapi.hook.param.PackageParam
* 获取此 APP 的通知图标是否被 Hook
* @param bean 图标 bean
*/
fun PackageParam.isAppNotifyHookOf(bean: IconDataBean) = prefs.getBoolean(key = bean.toEnabledName(), default = bean.isEnabled)
fun PackageParam.isAppNotifyHookOf(bean: IconDataBean) = prefs.getBoolean(bean.toEnabledName(), bean.isEnabled)
/**
* 获取此 APP 的通知图标是否被 Hook
* @param bean 图标 bean
*/
fun Context.isAppNotifyHookOf(bean: IconDataBean) = modulePrefs.getBoolean(key = bean.toEnabledName(), default = bean.isEnabled)
fun Context.isAppNotifyHookOf(bean: IconDataBean) = modulePrefs.getBoolean(bean.toEnabledName(), bean.isEnabled)
/**
* 设置 Hook 此 APP 的通知图标
* @param bean 图标 bean
* @param isHook 是否 Hook
*/
fun Context.putAppNotifyHookOf(bean: IconDataBean, isHook: Boolean) =
modulePrefs.putBoolean(key = bean.toEnabledName(), value = isHook)
fun Context.putAppNotifyHookOf(bean: IconDataBean, isHook: Boolean) = modulePrefs.putBoolean(bean.toEnabledName(), isHook)
/**
* 获取此 APP 的通知图标是否被全部 Hook
* @param bean 图标 bean
*/
fun PackageParam.isAppNotifyHookAllOf(bean: IconDataBean) =
prefs.getBoolean(key = bean.toEnabledAllName(), default = bean.isEnabledAll)
fun PackageParam.isAppNotifyHookAllOf(bean: IconDataBean) = prefs.getBoolean(bean.toEnabledAllName(), bean.isEnabledAll)
/**
* 获取此 APP 的通知图标是否被全部 Hook
* @param bean 图标 bean
*/
fun Context.isAppNotifyHookAllOf(bean: IconDataBean) =
modulePrefs.getBoolean(key = bean.toEnabledAllName(), default = bean.isEnabledAll)
fun Context.isAppNotifyHookAllOf(bean: IconDataBean) = modulePrefs.getBoolean(bean.toEnabledAllName(), bean.isEnabledAll)
/**
* 设置全部 Hook 此 APP 的通知图标
* @param bean 图标 bean
* @param isHook 是否 Hook
*/
fun Context.putAppNotifyHookAllOf(bean: IconDataBean, isHook: Boolean) =
modulePrefs.putBoolean(key = bean.toEnabledAllName(), value = isHook)
fun Context.putAppNotifyHookAllOf(bean: IconDataBean, isHook: Boolean) = modulePrefs.putBoolean(bean.toEnabledAllName(), isHook)

View File

@@ -28,8 +28,8 @@ import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
import com.fankes.coloros.notify.bean.IconDataBean
import com.fankes.coloros.notify.hook.HookConst.NOTIFY_ICON_DATAS
import com.fankes.coloros.notify.utils.*
import com.fankes.coloros.notify.data.DataConst
import com.fankes.coloros.notify.utils.factory.*
import com.highcapable.yukihookapi.hook.factory.modulePrefs
import com.highcapable.yukihookapi.hook.param.PackageParam
import org.json.JSONArray
@@ -101,7 +101,7 @@ class IconPackParams(private val context: Context? = null, private val param: Pa
* 已存储的 JSON 数据
* @return [String]
*/
internal val storageDataJson get() = (context?.modulePrefs ?: param?.prefs)?.getString(NOTIFY_ICON_DATAS)
internal val storageDataJson get() = (context?.modulePrefs ?: param?.prefs)?.direct()?.get(DataConst.NOTIFY_ICON_DATAS)
/**
* 获取图标数据
@@ -188,5 +188,5 @@ class IconPackParams(private val context: Context? = null, private val param: Pa
* 保存图标数据
* @param dataJson 图标数据 JSON
*/
fun save(dataJson: String) = context?.modulePrefs?.putString(NOTIFY_ICON_DATAS, dataJson)
fun save(dataJson: String) = context?.modulePrefs?.put(DataConst.NOTIFY_ICON_DATAS, dataJson)
}

View File

@@ -0,0 +1,36 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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/26.
*/
package com.fankes.coloros.notify.service
import android.service.quicksettings.TileService
import com.fankes.coloros.notify.ui.activity.ConfigureActivity
import com.fankes.coloros.notify.utils.factory.navigate
class QuickStartTileService : TileService() {
override fun onClick() {
super.onClick()
/** 启动通知图标优化列表窗口 */
navigate<ConfigureActivity>()
}
}

View File

@@ -1,447 +0,0 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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("SetTextI18n", "InflateParams", "DEPRECATION")
package com.fankes.coloros.notify.ui
import android.app.ProgressDialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.ListView
import android.widget.TextView
import androidx.constraintlayout.utils.widget.ImageFilterView
import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged
import com.fankes.coloros.notify.R
import com.fankes.coloros.notify.bean.IconDataBean
import com.fankes.coloros.notify.hook.HookConst.SOURCE_SYNC_WAY
import com.fankes.coloros.notify.hook.HookConst.SOURCE_SYNC_WAY_CUSTOM_URL
import com.fankes.coloros.notify.hook.HookConst.TYPE_SOURCE_SYNC_WAY_1
import com.fankes.coloros.notify.hook.HookConst.TYPE_SOURCE_SYNC_WAY_2
import com.fankes.coloros.notify.hook.HookConst.TYPE_SOURCE_SYNC_WAY_3
import com.fankes.coloros.notify.hook.factory.isAppNotifyHookAllOf
import com.fankes.coloros.notify.hook.factory.isAppNotifyHookOf
import com.fankes.coloros.notify.hook.factory.putAppNotifyHookAllOf
import com.fankes.coloros.notify.hook.factory.putAppNotifyHookOf
import com.fankes.coloros.notify.param.IconPackParams
import com.fankes.coloros.notify.ui.base.BaseActivity
import com.fankes.coloros.notify.utils.*
import com.fankes.coloros.notify.view.MaterialSwitch
import com.google.android.material.radiobutton.MaterialRadioButton
import com.google.android.material.textfield.TextInputEditText
import com.highcapable.yukihookapi.hook.factory.modulePrefs
import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus
class ConfigureActivity : BaseActivity() {
/** 当前筛选条件 */
private var filterText = ""
/** 回调适配器改变 */
private var onChanged: (() -> Unit)? = null
/** 回调滚动事件改变 */
private var onScrollEvent: ((Boolean) -> Unit)? = null
/** 全部的通知优化图标数据 */
private var iconAllDatas = ArrayList<IconDataBean>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_config)
/** 检查激活状态 */
if (!YukiHookModuleStatus.isActive()) {
showDialog {
title = "模块没有激活"
msg = "模块没有激活,你无法使用这里的功能,请先激活模块。"
confirmButton(text = "我知道了") { finish() }
noCancelable()
}
return
}
/** 返回按钮点击事件 */
findViewById<View>(R.id.title_back_icon).setOnClickListener { onBackPressed() }
/** 刷新适配器结果相关 */
refreshAdapterResult()
/** 设置上下按钮点击事件 */
findViewById<View>(R.id.config_title_up).setOnClickListener {
snake(msg = "滚动到顶部")
onScrollEvent?.invoke(false)
}
findViewById<View>(R.id.config_title_down).setOnClickListener {
snake(msg = "滚动到底部")
onScrollEvent?.invoke(true)
}
/** 设置过滤按钮点击事件 */
findViewById<View>(R.id.config_title_filter).setOnClickListener {
showDialog {
title = "按条件过滤"
var editText: TextInputEditText
addView(R.layout.dia_icon_filter).apply {
editText = findViewById<TextInputEditText>(R.id.dia_icon_filter_input_edit).apply {
requestFocus()
invalidate()
if (filterText.isNotBlank()) {
setText(filterText)
setSelection(filterText.length)
}
}
}
confirmButton {
if (editText.text.toString().isNotBlank()) {
filterText = editText.text.toString().trim()
refreshAdapterResult()
} else {
toast(msg = "条件不能为空")
it.performClick()
}
}
cancelButton()
if (filterText.isNotBlank())
neutralButton(text = "清除条件") {
filterText = ""
refreshAdapterResult()
}
}
}
/** 设置同步列表按钮点击事件 */
findViewById<View>(R.id.config_title_sync).setOnClickListener { onStartRefresh() }
/** 设置列表元素和 Adapter */
findViewById<ListView>(R.id.config_list_view).apply {
adapter = object : BaseAdapter() {
private val inflater = LayoutInflater.from(context)
override fun getCount() = iconDatas.size
override fun getItem(position: Int) = iconDatas[position]
override fun getItemId(position: Int) = position.toLong()
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
var cView = convertView
val holder: ViewHolder
if (convertView == null) {
holder = ViewHolder()
cView = inflater.inflate(R.layout.adapter_config, null).also {
holder.appIcon = it.findViewById(R.id.adp_app_icon)
holder.appName = it.findViewById(R.id.adp_app_name)
holder.pkgName = it.findViewById(R.id.adp_app_pkg_name)
holder.cbrName = it.findViewById(R.id.adp_cbr_name)
holder.switchOpen = it.findViewById(R.id.adp_app_open_switch)
holder.switchAll = it.findViewById(R.id.adp_app_all_switch)
}
cView.tag = holder
} else holder = convertView.tag as ViewHolder
getItem(position).also {
holder.appIcon.setImageBitmap(it.iconBitmap)
(if (it.iconColor != 0) it.iconColor else resources.getColor(R.color.colorTextGray)).also { color ->
holder.appIcon.setColorFilter(color)
holder.appName.setTextColor(color)
}
holder.appName.text = it.appName
holder.pkgName.text = it.packageName
holder.cbrName.text = "贡献者:" + it.contributorName
isAppNotifyHookOf(it).also { e ->
holder.switchOpen.isChecked = e
holder.switchAll.isEnabled = e
}
holder.switchOpen.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
putAppNotifyHookOf(it, b)
holder.switchAll.isEnabled = b
SystemUITool.showNeedRestartSnake(context = this@ConfigureActivity)
}
holder.switchAll.isChecked = isAppNotifyHookAllOf(it)
holder.switchAll.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
putAppNotifyHookAllOf(it, b)
SystemUITool.showNeedRestartSnake(context = this@ConfigureActivity)
}
}
return cView!!
}
inner class ViewHolder {
lateinit var appIcon: ImageFilterView
lateinit var appName: TextView
lateinit var pkgName: TextView
lateinit var cbrName: TextView
lateinit var switchOpen: MaterialSwitch
lateinit var switchAll: MaterialSwitch
}
}.apply {
setOnItemLongClickListener { _, _, p, _ ->
showDialog {
title = "复制“${iconDatas[p].appName}”的规则"
msg = "是否复制单条规则到剪贴板?"
confirmButton { copyToClipboard(iconDatas[p].toString()) }
cancelButton()
}
true
}
onChanged = { notifyDataSetChanged() }
}
onScrollEvent = { post { setSelection(if (it) iconDatas.lastIndex else 0) } }
}
/** 设置点击事件 */
findViewById<View>(R.id.config_cbr_button).setOnClickListener {
openBrowser(url = "https://github.com/fankes/AndroidNotifyIconAdapt/blob/main/CONTRIBUTING.md")
}
/** 装载数据 */
mockLocalData()
/** 更新数据 */
onStartRefresh()
}
/** 装载或刷新本地数据 */
private fun mockLocalData() {
iconAllDatas = IconPackParams(context = this).iconDatas
refreshAdapterResult()
}
/** 首次进入或更新数据 */
private fun onStartRefresh() =
showDialog {
title = "同步列表"
var sourceType = modulePrefs.getInt(SOURCE_SYNC_WAY, TYPE_SOURCE_SYNC_WAY_1)
var customUrl = modulePrefs.getString(SOURCE_SYNC_WAY_CUSTOM_URL)
addView(R.layout.dia_source_from).apply {
val radio1 = findViewById<MaterialRadioButton>(R.id.dia_sf_rd1)
val radio2 = findViewById<MaterialRadioButton>(R.id.dia_sf_rd2)
val radio3 = findViewById<MaterialRadioButton>(R.id.dia_sf_rd3)
val edLin = findViewById<View>(R.id.dia_sf_text_lin)
findViewById<TextInputEditText>(R.id.dia_sf_text).apply {
if (customUrl.isNotBlank()) {
setText(customUrl)
setSelection(customUrl.length)
}
doOnTextChanged { text, _, _, _ ->
customUrl = text.toString()
modulePrefs.putString(SOURCE_SYNC_WAY_CUSTOM_URL, text.toString())
}
}
edLin.isVisible = sourceType == TYPE_SOURCE_SYNC_WAY_3
radio1.isChecked = sourceType == TYPE_SOURCE_SYNC_WAY_1
radio2.isChecked = sourceType == TYPE_SOURCE_SYNC_WAY_2
radio3.isChecked = sourceType == TYPE_SOURCE_SYNC_WAY_3
radio1.setOnClickListener {
radio2.isChecked = false
radio3.isChecked = false
edLin.isVisible = false
sourceType = TYPE_SOURCE_SYNC_WAY_1
modulePrefs.putInt(SOURCE_SYNC_WAY, TYPE_SOURCE_SYNC_WAY_1)
}
radio2.setOnClickListener {
radio1.isChecked = false
radio3.isChecked = false
edLin.isVisible = false
sourceType = TYPE_SOURCE_SYNC_WAY_2
modulePrefs.putInt(SOURCE_SYNC_WAY, TYPE_SOURCE_SYNC_WAY_2)
}
radio3.setOnClickListener {
radio1.isChecked = false
radio2.isChecked = false
edLin.isVisible = true
sourceType = TYPE_SOURCE_SYNC_WAY_3
modulePrefs.putInt(SOURCE_SYNC_WAY, TYPE_SOURCE_SYNC_WAY_3)
}
}
confirmButton {
when (sourceType) {
TYPE_SOURCE_SYNC_WAY_1 -> onRefreshing(url = "https://raw.fastgit.org/fankes/AndroidNotifyIconAdapt/main")
TYPE_SOURCE_SYNC_WAY_2 -> onRefreshing(url = "https://raw.githubusercontent.com/fankes/AndroidNotifyIconAdapt/main")
TYPE_SOURCE_SYNC_WAY_3 ->
if (customUrl.isNotBlank())
if (customUrl.startsWith("http://") || customUrl.startsWith("https://"))
onRefreshingCustom(customUrl)
else snake(msg = "同步地址不是一个合法的 URL")
else snake(msg = "同步地址不能为空")
else -> snake(msg = "同步类型错误")
}
}
cancelButton()
neutralButton(text = "自定义规则") {
showDialog {
title = "自定义规则"
var editText: TextInputEditText
addView(R.layout.dia_source_from_string).apply {
editText = findViewById<TextInputEditText>(R.id.dia_sfs_input_edit).apply {
requestFocus()
invalidate()
}
}
IconPackParams(context = this@ConfigureActivity).also { params ->
confirmButton(text = "合并") {
editText.text.toString().also { jsonString ->
when {
jsonString.isNotBlank() && params.isNotVaildJson(jsonString) -> snake(msg = "不是有效的 JSON 数据")
jsonString.isNotBlank() -> {
params.save(
params.splicingJsonArray(
dataJson1 = params.storageDataJson ?: "[]",
dataJson2 = jsonString.takeIf { params.isJsonArray(it) } ?: "[$jsonString]"
)
)
filterText = ""
mockLocalData()
SystemUITool.showNeedUpdateApplySnake(context = this@ConfigureActivity)
}
else -> snake(msg = "请输入有效内容")
}
}
}
cancelButton(text = "覆盖") {
editText.text.toString().also { jsonString ->
when {
jsonString.isNotBlank() && params.isNotVaildJson(jsonString) -> snake(msg = "不是有效的 JSON 数据")
jsonString.isNotBlank() -> {
params.save(dataJson = jsonString.takeIf { params.isJsonArray(it) } ?: "[$jsonString]")
filterText = ""
mockLocalData()
SystemUITool.showNeedUpdateApplySnake(context = this@ConfigureActivity)
}
else -> snake(msg = "请输入有效内容")
}
}
}
}
neutralButton(text = "取消")
}
}
}
/**
* 开始更新数据
* @param url
*/
private fun onRefreshing(url: String) = ClientRequestTool.checkingInternetConnect(context = this) {
ProgressDialog(this).apply {
setDefaultStyle(context = this@ConfigureActivity)
setCancelable(false)
setTitle("同步中")
setMessage("正在同步 OS 数据")
show()
}.also {
ClientRequestTool.wait(
context = this,
url = "$url/OS/ColorOS/NotifyIconsSupportConfig.json"
) { isDone1, ctOS ->
it.setMessage("正在同步 APP 数据")
ClientRequestTool.wait(
context = this,
url = "$url/APP/NotifyIconsSupportConfig.json"
) { isDone2, ctAPP ->
it.cancel()
IconPackParams(context = this).also { params ->
if (isDone1 && isDone2) params.splicingJsonArray(ctOS, ctAPP).also {
when {
params.isHackString(it) -> snake(msg = "请求需要验证,请尝试魔法上网或关闭魔法")
params.isNotVaildJson(it) -> snake(msg = "在线规则发生问题,请稍后重试")
params.isCompareDifferent(it) -> {
params.save(it)
filterText = ""
mockLocalData()
SystemUITool.showNeedUpdateApplySnake(context = this)
}
else -> snake(msg = "列表数据已是最新")
}
} else showDialog {
title = "连接失败"
msg = "连接失败,错误如下:\n${if (!isDone1) ctOS else ctAPP}"
confirmButton(text = "解决方案") {
openBrowser(url = "https://www.baidu.com/s?wd=github%2Braw%2B%E6%97%A0%E6%B3%95%E8%AE%BF%E9%97%AE")
}
cancelButton()
}
}
}
}
}
}
/**
* 开始更新数据
* @param url
*/
private fun onRefreshingCustom(url: String) = ClientRequestTool.checkingInternetConnect(context = this) {
ProgressDialog(this).apply {
setDefaultStyle(context = this@ConfigureActivity)
setCancelable(false)
setTitle("同步中")
setMessage("正在通过自定义地址同步数据")
show()
}.also {
ClientRequestTool.wait(
context = this,
url = url
) { isDone, content ->
it.cancel()
IconPackParams(context = this).also { params ->
if (isDone)
when {
params.isHackString(content) -> snake(msg = "请求需要验证,请尝试魔法上网或关闭魔法")
params.isNotVaildJson(content) -> snake(msg = "目标地址不是有效的 JSON 数据")
params.isCompareDifferent(content) -> {
params.save(content)
filterText = ""
mockLocalData()
SystemUITool.showNeedUpdateApplySnake(context = this)
}
else -> snake(msg = "列表数据已是最新")
}
else showDialog {
title = "连接失败"
msg = "连接失败,错误如下:\n$content"
confirmButton(text = "我知道了")
}
}
}
}
}
/** 刷新适配器结果相关 */
private fun refreshAdapterResult() {
onChanged?.invoke()
findViewById<TextView>(R.id.config_title_count_text).text =
if (filterText.isBlank()) "已适配 ${iconDatas.size} 个 APP 的通知图标"
else "${filterText}” 匹配到 ${iconDatas.size} 个结果"
findViewById<TextView>(R.id.config_list_no_data_view).apply {
text = if (iconAllDatas.isEmpty()) "噫,竟然什么都没有~\n请点击右上角同步按钮获取云端数据" else "噫,竟然什么都没找到~"
isVisible = iconDatas.isEmpty()
}
}
/**
* 当前结果下的图标数组
* @return [Array]
*/
private val iconDatas
get() = if (filterText.isBlank()) iconAllDatas
else iconAllDatas.filter {
it.appName.lowercase().contains(filterText.lowercase()) || it.packageName.lowercase().contains(filterText.lowercase())
}
}

View File

@@ -1,174 +0,0 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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/26.
*/
@file:Suppress("SetTextI18n")
package com.fankes.coloros.notify.ui
import android.content.ComponentName
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.view.View
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.widget.SwitchCompat
import androidx.constraintlayout.utils.widget.ImageFilterView
import androidx.core.view.isVisible
import com.fankes.coloros.notify.BuildConfig
import com.fankes.coloros.notify.R
import com.fankes.coloros.notify.hook.HookConst.ENABLE_ANDROID12_STYLE
import com.fankes.coloros.notify.hook.HookConst.ENABLE_HIDE_ICON
import com.fankes.coloros.notify.hook.HookConst.ENABLE_MODULE
import com.fankes.coloros.notify.hook.HookConst.ENABLE_MODULE_LOG
import com.fankes.coloros.notify.hook.HookConst.ENABLE_NOTIFY_ICON_FIX
import com.fankes.coloros.notify.hook.HookConst.REMOVE_CHANGECP_NOTIFY
import com.fankes.coloros.notify.hook.HookConst.REMOVE_DEV_NOTIFY
import com.fankes.coloros.notify.ui.base.BaseActivity
import com.fankes.coloros.notify.utils.*
import com.highcapable.yukihookapi.hook.factory.modulePrefs
import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus
class MainActivity : BaseActivity() {
companion object {
/** 模块版本 */
private const val moduleVersion = BuildConfig.VERSION_NAME
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
/** 设置文本 */
findViewById<TextView>(R.id.main_text_version).text = "模块版本:$moduleVersion"
findViewById<TextView>(R.id.main_text_coloros_version).text = "系统版本:$colorOSVersion"
when {
/** 判断是否为 ColorOS 系统 */
isNotColorOS ->
showDialog {
title = "不是 ColorOS 系统"
msg = "此模块专为 ColorOS 系统打造,当前无法识别你的系统为 ColorOS所以模块无法工作。\n" +
"如有问题请联系 酷安 @星夜不荟"
confirmButton(text = "退出") { finish() }
noCancelable()
}
/** 判断是否 Hook */
YukiHookModuleStatus.isActive() -> {
findViewById<LinearLayout>(R.id.main_lin_status).setBackgroundResource(R.drawable.bg_green_round)
findViewById<ImageFilterView>(R.id.main_img_status).setImageResource(R.mipmap.ic_success)
findViewById<TextView>(R.id.main_text_status).text = "模块已激活"
}
else ->
showDialog {
title = "模块没有激活"
msg = "检测到模块没有激活,模块需要 Xposed 环境依赖," +
"同时需要系统拥有 Root 权限," +
"请自行查看本页面使用帮助与说明第二条。\n" +
"由于需要修改系统应用达到效果,模块不支持太极阴、应用转生。"
confirmButton(text = "我知道了")
noCancelable()
}
}
/** 初始化 View */
val moduleEnableSwitch = findViewById<SwitchCompat>(R.id.module_enable_switch)
val moduleEnableLogSwitch = findViewById<SwitchCompat>(R.id.module_enable_log_switch)
val devNotifyConfigItem = findViewById<View>(R.id.config_item_dev)
val a12StyleConfigItem = findViewById<View>(R.id.config_item_a12)
val notifyIconConfigItem = findViewById<View>(R.id.config_item_notify)
val devNotifyConfigSwitch = findViewById<SwitchCompat>(R.id.remove_dev_n_enable_switch)
val crcpNotifyConfigSwitch = findViewById<SwitchCompat>(R.id.remove_chargecp_n_enable_switch)
val a12StyleConfigSwitch = findViewById<SwitchCompat>(R.id.a12_style_enable_switch)
val hideIconInLauncherSwitch = findViewById<SwitchCompat>(R.id.hide_icon_in_launcher_switch)
val notifyIconFixSwitch = findViewById<SwitchCompat>(R.id.notify_icon_fix_switch)
val notifyIconFixButton = findViewById<View>(R.id.config_notify_app_button)
/** 获取 Sp 存储的信息 */
devNotifyConfigItem.isVisible = modulePrefs.getBoolean(ENABLE_MODULE, default = true)
a12StyleConfigItem.isVisible = modulePrefs.getBoolean(ENABLE_MODULE, default = true)
notifyIconConfigItem.isVisible = modulePrefs.getBoolean(ENABLE_MODULE, default = true)
moduleEnableLogSwitch.isVisible = modulePrefs.getBoolean(ENABLE_MODULE, default = true)
notifyIconFixButton.isVisible = modulePrefs.getBoolean(ENABLE_NOTIFY_ICON_FIX, default = true)
devNotifyConfigSwitch.isChecked = modulePrefs.getBoolean(REMOVE_DEV_NOTIFY, default = true)
crcpNotifyConfigSwitch.isChecked = modulePrefs.getBoolean(REMOVE_CHANGECP_NOTIFY, default = false)
a12StyleConfigSwitch.isChecked = modulePrefs.getBoolean(ENABLE_ANDROID12_STYLE, isUpperOfAndroidS)
moduleEnableSwitch.isChecked = modulePrefs.getBoolean(ENABLE_MODULE, default = true)
moduleEnableLogSwitch.isChecked = modulePrefs.getBoolean(ENABLE_MODULE_LOG, default = false)
hideIconInLauncherSwitch.isChecked = modulePrefs.getBoolean(ENABLE_HIDE_ICON)
notifyIconFixSwitch.isChecked = modulePrefs.getBoolean(ENABLE_NOTIFY_ICON_FIX, default = true)
moduleEnableSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(ENABLE_MODULE, b)
moduleEnableLogSwitch.isVisible = b
notifyIconConfigItem.isVisible = b
devNotifyConfigItem.isVisible = b
a12StyleConfigItem.isVisible = b
SystemUITool.showNeedRestartSnake(context = this)
}
moduleEnableLogSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(ENABLE_MODULE_LOG, b)
SystemUITool.showNeedRestartSnake(context = this)
}
hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(ENABLE_HIDE_ICON, b)
packageManager.setComponentEnabledSetting(
ComponentName(packageName, "com.fankes.coloros.notify.Home"),
if (b) PackageManager.COMPONENT_ENABLED_STATE_DISABLED else PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP
)
}
notifyIconFixSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(ENABLE_NOTIFY_ICON_FIX, b)
notifyIconFixButton.isVisible = b
SystemUITool.showNeedRestartSnake(context = this)
}
devNotifyConfigSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(REMOVE_DEV_NOTIFY, b)
SystemUITool.showNeedRestartSnake(context = this)
}
crcpNotifyConfigSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(REMOVE_CHANGECP_NOTIFY, b)
SystemUITool.showNeedRestartSnake(context = this)
}
a12StyleConfigSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(ENABLE_ANDROID12_STYLE, b)
SystemUITool.showNeedRestartSnake(context = this)
}
/** 通知图标优化名单按钮点击事件 */
notifyIconFixButton.setOnClickListener { startActivity(Intent(this, ConfigureActivity::class.java)) }
/** 重启按钮点击事件 */
findViewById<View>(R.id.title_restart_icon).setOnClickListener { SystemUITool.restartSystemUI(context = this) }
/** 恰饭! */
findViewById<View>(R.id.link_with_follow_me).setOnClickListener {
openBrowser(url = "https://www.coolapk.com/u/876977", packageName = "com.coolapk.market")
}
/** 项目地址点击事件 */
findViewById<View>(R.id.link_with_project_address).setOnClickListener {
openBrowser(url = "https://github.com/fankes/ColorOSNotifyIcon")
}
}
}

View File

@@ -0,0 +1,257 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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("SetTextI18n", "InflateParams", "DEPRECATION")
package com.fankes.coloros.notify.ui.activity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import androidx.core.view.isVisible
import com.fankes.coloros.notify.R
import com.fankes.coloros.notify.bean.IconDataBean
import com.fankes.coloros.notify.databinding.ActivityConfigBinding
import com.fankes.coloros.notify.databinding.AdapterConfigBinding
import com.fankes.coloros.notify.databinding.DiaIconFilterBinding
import com.fankes.coloros.notify.hook.factory.isAppNotifyHookAllOf
import com.fankes.coloros.notify.hook.factory.isAppNotifyHookOf
import com.fankes.coloros.notify.hook.factory.putAppNotifyHookAllOf
import com.fankes.coloros.notify.hook.factory.putAppNotifyHookOf
import com.fankes.coloros.notify.param.IconPackParams
import com.fankes.coloros.notify.ui.activity.base.BaseActivity
import com.fankes.coloros.notify.utils.factory.*
import com.fankes.coloros.notify.utils.tool.IconRuleManagerTool
import com.fankes.coloros.notify.utils.tool.SystemUITool
import com.highcapable.yukihookapi.hook.factory.isXposedModuleActive
class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
/** 当前筛选条件 */
private var filterText = ""
/** 回调适配器改变 */
private var onChanged: (() -> Unit)? = null
/** 回调滚动事件改变 */
private var onScrollEvent: ((Boolean) -> Unit)? = null
/** 全部的通知图标优化数据 */
private var iconAllDatas = ArrayList<IconDataBean>()
override fun onCreate() {
/** 检查激活状态 */
if (isXposedModuleActive.not()) {
showDialog {
title = "模块没有激活"
msg = "模块没有激活,你无法使用这里的功能,请先激活模块。"
confirmButton(text = "我知道了") { finish() }
noCancelable()
}
return
}
/** 返回按钮点击事件 */
binding.titleBackIcon.setOnClickListener { onBackPressed() }
/** 刷新适配器结果相关 */
refreshAdapterResult()
/** 设置上下按钮点击事件 */
binding.configTitleUp.setOnClickListener {
snake(msg = "滚动到顶部")
onScrollEvent?.invoke(false)
}
binding.configTitleDown.setOnClickListener {
snake(msg = "滚动到底部")
onScrollEvent?.invoke(true)
}
/** 设置过滤按钮点击事件 */
binding.configTitleFilter.setOnClickListener {
showDialog {
title = "按条件过滤"
val editText = bind<DiaIconFilterBinding>().diaIconFilterInputEdit.apply {
requestFocus()
invalidate()
if (filterText.isNotBlank()) {
setText(filterText)
setSelection(filterText.length)
}
}
confirmButton {
if (editText.text.toString().isNotBlank()) {
filterText = editText.text.toString().trim()
refreshAdapterResult()
} else {
toast(msg = "条件不能为空")
it.performClick()
}
}
cancelButton()
if (filterText.isNotBlank())
neutralButton(text = "清除条件") {
filterText = ""
refreshAdapterResult()
}
}
}
/** 设置同步列表按钮点击事件 */
binding.configTitleSync.setOnClickListener { onStartRefresh() }
/** 设置列表元素和 Adapter */
binding.configListView.apply {
adapter = object : BaseAdapter() {
override fun getCount() = iconDatas.size
override fun getItem(position: Int) = iconDatas[position]
override fun getItemId(position: Int) = position.toLong()
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
var cView = convertView
val holder: AdapterConfigBinding
if (convertView == null) {
holder = AdapterConfigBinding.inflate(LayoutInflater.from(context))
cView = holder.root
cView.tag = holder
} else holder = convertView.tag as AdapterConfigBinding
getItem(position).also {
holder.adpAppIcon.setImageBitmap(it.iconBitmap)
(if (it.iconColor != 0) it.iconColor else resources.getColor(R.color.colorTextGray)).also { color ->
holder.adpAppIcon.setColorFilter(color)
holder.adpAppName.setTextColor(color)
}
holder.adpAppName.text = it.appName
holder.adpAppPkgName.text = it.packageName
holder.adpCbrName.text = "贡献者:" + it.contributorName
isAppNotifyHookOf(it).also { e ->
holder.adpAppOpenSwitch.isChecked = e
holder.adpAppAllSwitch.isEnabled = e
}
holder.adpAppOpenSwitch.setOnCheckedChangeListener { btn, b ->
if (btn.isPressed.not()) return@setOnCheckedChangeListener
putAppNotifyHookOf(it, b)
holder.adpAppAllSwitch.isEnabled = b
SystemUITool.refreshSystemUI(context = this@ConfigureActivity)
}
holder.adpAppAllSwitch.isChecked = isAppNotifyHookAllOf(it)
holder.adpAppAllSwitch.setOnCheckedChangeListener { btn, b ->
if (btn.isPressed.not()) return@setOnCheckedChangeListener
putAppNotifyHookAllOf(it, b)
SystemUITool.refreshSystemUI(context = this@ConfigureActivity)
}
}
return cView!!
}
}.apply {
setOnItemLongClickListener { _, _, p, _ ->
showDialog {
title = "复制“${iconDatas[p].appName}”的规则"
msg = "是否复制单条规则到剪贴板?"
confirmButton { copyToClipboard(iconDatas[p].toString()) }
cancelButton()
}
true
}
onChanged = { notifyDataSetChanged() }
}
onScrollEvent = { post { setSelection(if (it) iconDatas.lastIndex else 0) } }
}
/** 设置点击事件 */
binding.configCbrButton.setOnClickListener {
openBrowser(url = "https://github.com/fankes/AndroidNotifyIconAdapt/blob/main/CONTRIBUTING.md")
}
/** 装载数据 */
mockLocalData()
/** 更新数据 */
when {
intent?.getBooleanExtra("isNewAppSupport", false) == true ->
showDialog {
val appName = intent?.getStringExtra("appName") ?: ""
val pkgName = intent?.getStringExtra("pkgName") ?: ""
title = "新安装应用通知图标适配"
msg = "你已安装 $appName($pkgName)\n\n" +
"此应用未在通知优化名单中发现适配数据,若此应用发送的通知为彩色图标," +
"可随时点击本页面下方的“贡献通知图标优化名单”按钮提交贡献或请求适配。\n\n" +
"若你已知晓此应用会遵守原生通知图标规范,可忽略此提示。\n\n" +
"你可以现在立即同步适配列表,以获取最新的适配数据。"
confirmButton(text = "同步列表") { onStartRefresh() }
cancelButton(text = "复制名称+包名") { copyToClipboard(content = "$appName($pkgName)") }
neutralButton(text = "取消")
noCancelable()
}
intent?.getBooleanExtra("isShowUpdDialog", true) == true -> onStartRefresh()
}
/** 清除数据 */
intent?.apply {
removeExtra("isNewAppSupport")
removeExtra("isShowUpdDialog")
}
}
/** 开始手动同步 */
private fun onStartRefresh() =
IconRuleManagerTool.syncByHand(context = this) {
filterText = ""
mockLocalData()
}
/** 装载或刷新本地数据 */
private fun mockLocalData() {
iconAllDatas = IconPackParams(context = this).iconDatas
refreshAdapterResult()
}
/** 刷新适配器结果相关 */
private fun refreshAdapterResult() {
onChanged?.invoke()
binding.configTitleCountText.text =
if (filterText.isBlank()) "已适配 ${iconDatas.size} 个 APP 的通知图标"
else "${filterText}” 匹配到 ${iconDatas.size} 个结果"
binding.configListNoDataView.apply {
text = if (iconAllDatas.isEmpty()) "噫,竟然什么都没有~\n请点击右上角同步按钮获取云端数据" else "噫,竟然什么都没找到~"
isVisible = iconDatas.isEmpty()
}
}
/**
* 当前结果下的图标数组
* @return [Array]
*/
private val iconDatas
get() = if (filterText.isBlank()) iconAllDatas
else iconAllDatas.filter {
it.appName.lowercase().contains(filterText.lowercase()) || it.packageName.lowercase().contains(filterText.lowercase())
}
override fun onBackPressed() {
if (MainActivity.isActivityLive.not())
showDialog {
title = "提示"
msg = "要返回模块主页吗?"
confirmButton {
super.onBackPressed()
navigate<MainActivity>()
}
cancelButton { super.onBackPressed() }
}
else super.onBackPressed()
}
}

View File

@@ -0,0 +1,297 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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/26.
*/
@file:Suppress("SetTextI18n")
package com.fankes.coloros.notify.ui.activity
import android.content.ComponentName
import android.content.pm.PackageManager
import android.widget.SeekBar
import android.widget.SeekBar.OnSeekBarChangeListener
import androidx.core.view.isVisible
import com.fankes.coloros.notify.BuildConfig
import com.fankes.coloros.notify.R
import com.fankes.coloros.notify.data.DataConst
import com.fankes.coloros.notify.databinding.ActivityMainBinding
import com.fankes.coloros.notify.param.IconPackParams
import com.fankes.coloros.notify.ui.activity.base.BaseActivity
import com.fankes.coloros.notify.utils.factory.*
import com.fankes.coloros.notify.utils.tool.GithubReleaseTool
import com.fankes.coloros.notify.utils.tool.SystemUITool
import com.highcapable.yukihookapi.hook.factory.isXposedModuleActive
import com.highcapable.yukihookapi.hook.factory.modulePrefs
import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus
class MainActivity : BaseActivity<ActivityMainBinding>() {
companion object {
/** 窗口是否启动 */
internal var isActivityLive = false
/** 模块版本 */
private const val moduleVersion = BuildConfig.VERSION_NAME
/** 预发布的版本标识 */
private const val pendingFlag = ""
}
/** 模块是否可用 */
private var isModuleRegular = false
/** 模块是否有效 */
private var isModuleValied = false
override fun onCreate() {
/** 设置可用性 */
isActivityLive = true
/** 设置文本 */
binding.mainTextVersion.text = "模块版本:$moduleVersion $pendingFlag"
binding.mainTextColorOsVersion.text = "系统版本:$colorOSFullVersion"
/** 检查更新 */
GithubReleaseTool.checkingForUpdate(context = this, moduleVersion) { version, function ->
binding.mainTextReleaseVersion.apply {
text = "点击更新 $version"
isVisible = true
setOnClickListener { function() }
}
}
when {
/** 判断是否为 ColorOS 系统 */
isNotColorOS ->
showDialog {
title = "不是 ColorOS 系统"
msg = "此模块专为 ColorOS 系统打造,当前无法识别你的系统为 ColorOS所以模块无法工作。\n" +
"如有问题请联系 酷安 @星夜不荟"
confirmButton(text = "退出") { finish() }
noCancelable()
}
/** 判断是否 Hook */
isXposedModuleActive -> {
if (IconPackParams(context = this).iconDatas.isEmpty() && modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX))
showDialog {
title = "配置通知图标优化名单"
msg = "模块需要获取在线规则以更新“通知图标优化名单”,它现在是空的,这看起来是你第一次使用模块,请首先进行配置才可以使用相关功能。\n" +
"你可以随时在本页面下方找到“配置通知图标优化名单”手动前往。"
confirmButton(text = "前往") { navigate<ConfigureActivity>() }
cancelButton()
noCancelable()
}
if (isNotNoificationEnabled && modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX))
showDialog {
title = "模块的通知权限已关闭"
msg = "请开启通知权限,以确保你能收到通知优化图标在线规则的更新。"
confirmButton { openNotifySetting() }
cancelButton()
noCancelable()
}
}
else ->
showDialog {
title = "模块没有激活"
msg = "检测到模块没有激活,模块需要 Xposed 环境依赖," +
"同时需要系统拥有 Root 权限," +
"请自行查看本页面使用帮助与说明第二条。\n" +
"由于需要修改系统应用达到效果,模块不支持太极阴、应用转生。"
confirmButton(text = "我知道了")
noCancelable()
}
}
var notifyIconAutoSyncTime = modulePrefs.get(DataConst.NOTIFY_ICON_FIX_AUTO_TIME)
binding.devNotifyConfigItem.isVisible = modulePrefs.get(DataConst.ENABLE_MODULE)
binding.a12StyleConfigItem.isVisible = modulePrefs.get(DataConst.ENABLE_MODULE)
binding.notifyIconConfigItem.isVisible = modulePrefs.get(DataConst.ENABLE_MODULE)
binding.notifyIconFixButton.isVisible = modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX)
binding.notifyIconFixNotifyItem.isVisible = modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX)
binding.notifyIconAutoSyncItem.isVisible = modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX)
binding.notifyIconAutoSyncChildItem.isVisible = modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX_AUTO)
binding.notifyPanelConfigSeekbar.isVisible = modulePrefs.get(DataConst.ENABLE_NOTIFY_PANEL_ALPHA)
binding.notifyPanelConfigTextPanel.isVisible = modulePrefs.get(DataConst.ENABLE_NOTIFY_PANEL_ALPHA)
binding.devNotifyConfigSwitch.isChecked = modulePrefs.get(DataConst.REMOVE_DEV_NOTIFY)
binding.crcpNotifyConfigSwitch.isChecked = modulePrefs.get(DataConst.REMOVE_CHANGECP_NOTIFY)
binding.dndNotifyConfigSwitch.isChecked = modulePrefs.get(DataConst.REMOVE_DNDALERT_NOTIFY)
binding.a12StyleConfigSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_ANDROID12_STYLE)
binding.moduleEnableSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_MODULE)
binding.moduleEnableLogSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_MODULE_LOG)
binding.hideIconInLauncherSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_HIDE_ICON)
binding.notifyIconFixSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX)
binding.notifyIconFixNotifySwitch.isChecked = modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX_NOTIFY)
binding.notifyIconAutoSyncSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX_AUTO)
binding.notifyPanelConfigSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_NOTIFY_PANEL_ALPHA)
binding.notifyPanelConfigSeekbar.progress = modulePrefs.get(DataConst.NOTIFY_PANEL_ALPHA)
binding.notifyPanelConfigText.text = modulePrefs.get(DataConst.NOTIFY_PANEL_ALPHA).toString()
binding.notifyIconAutoSyncText.text = notifyIconAutoSyncTime
binding.moduleEnableSwitch.setOnCheckedChangeListener { btn, b ->
if (btn.isPressed.not()) return@setOnCheckedChangeListener
modulePrefs.put(DataConst.ENABLE_MODULE, b)
binding.moduleEnableLogSwitch.isVisible = b
binding.notifyIconConfigItem.isVisible = b
binding.devNotifyConfigItem.isVisible = b
binding.a12StyleConfigItem.isVisible = b
SystemUITool.showNeedRestartSnake(context = this)
}
binding.moduleEnableLogSwitch.setOnCheckedChangeListener { btn, b ->
if (btn.isPressed.not()) return@setOnCheckedChangeListener
modulePrefs.put(DataConst.ENABLE_MODULE_LOG, b)
SystemUITool.showNeedRestartSnake(context = this)
}
binding.hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b ->
if (btn.isPressed.not()) return@setOnCheckedChangeListener
modulePrefs.put(DataConst.ENABLE_HIDE_ICON, b)
packageManager.setComponentEnabledSetting(
ComponentName(packageName, "com.fankes.coloros.notify.Home"),
if (b) PackageManager.COMPONENT_ENABLED_STATE_DISABLED else PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP
)
}
binding.notifyIconFixSwitch.setOnCheckedChangeListener { btn, b ->
if (btn.isPressed.not()) return@setOnCheckedChangeListener
modulePrefs.put(DataConst.ENABLE_NOTIFY_ICON_FIX, b)
binding.notifyIconFixButton.isVisible = b
binding.notifyIconFixNotifyItem.isVisible = b
binding.notifyIconAutoSyncItem.isVisible = b
SystemUITool.refreshSystemUI(context = this)
}
binding.notifyIconFixNotifySwitch.setOnCheckedChangeListener { btn, b ->
if (btn.isPressed.not()) return@setOnCheckedChangeListener
modulePrefs.put(DataConst.ENABLE_NOTIFY_ICON_FIX_NOTIFY, b)
SystemUITool.refreshSystemUI(context = this, isRefreshCacheOnly = true)
}
binding.devNotifyConfigSwitch.setOnCheckedChangeListener { btn, b ->
if (btn.isPressed.not()) return@setOnCheckedChangeListener
modulePrefs.put(DataConst.REMOVE_DEV_NOTIFY, b)
SystemUITool.refreshSystemUI(context = this, isRefreshCacheOnly = true)
}
binding.crcpNotifyConfigSwitch.setOnCheckedChangeListener { btn, b ->
if (btn.isPressed.not()) return@setOnCheckedChangeListener
modulePrefs.put(DataConst.REMOVE_CHANGECP_NOTIFY, b)
SystemUITool.refreshSystemUI(context = this, isRefreshCacheOnly = true)
}
binding.dndNotifyConfigSwitch.setOnCheckedChangeListener { btn, b ->
if (btn.isPressed.not()) return@setOnCheckedChangeListener
modulePrefs.put(DataConst.REMOVE_DNDALERT_NOTIFY, b)
SystemUITool.refreshSystemUI(context = this, isRefreshCacheOnly = true)
}
binding.a12StyleConfigSwitch.setOnCheckedChangeListener { btn, b ->
if (btn.isPressed.not()) return@setOnCheckedChangeListener
modulePrefs.put(DataConst.ENABLE_ANDROID12_STYLE, b)
SystemUITool.refreshSystemUI(context = this)
}
binding.notifyPanelConfigSwitch.setOnCheckedChangeListener { btn, b ->
if (btn.isPressed.not()) return@setOnCheckedChangeListener
modulePrefs.put(DataConst.ENABLE_NOTIFY_PANEL_ALPHA, b)
binding.notifyPanelConfigTextPanel.isVisible = b
binding.notifyPanelConfigSeekbar.isVisible = b
SystemUITool.refreshSystemUI(context = this)
}
binding.notifyIconAutoSyncSwitch.setOnCheckedChangeListener { btn, b ->
if (btn.isPressed.not()) return@setOnCheckedChangeListener
modulePrefs.put(DataConst.ENABLE_NOTIFY_ICON_FIX_AUTO, b)
binding.notifyIconAutoSyncChildItem.isVisible = b
SystemUITool.refreshSystemUI(context = this, isRefreshCacheOnly = true)
}
binding.notifyPanelConfigSeekbar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
binding.notifyPanelConfigText.text = progress.toString()
}
override fun onStopTrackingTouch(seekBar: SeekBar) {
modulePrefs.put(DataConst.NOTIFY_PANEL_ALPHA, seekBar.progress)
SystemUITool.refreshSystemUI(context = this@MainActivity)
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
})
/** 通知图标优化名单按钮点击事件 */
binding.notifyIconFixButton.setOnClickListener { navigate<ConfigureActivity>() }
/** 自动更新在线规则修改时间按钮点击事件 */
binding.notifyIconAutoSyncButton.setOnClickListener {
showTimePicker(notifyIconAutoSyncTime) {
showDialog {
title = "每天 $it 自动更新"
msg = "设置保存后将在每天 $it 自动同步名单到最新云端数据,若数据已是最新则不会显示任何提示,否则会发送一条通知。\n\n" +
"请确保:\n\n" +
"1.模块没有被禁止前台以及后台联网权限\n" +
"2.模块没有被禁止被其它 APP 关联唤醒\n" +
"3.模块的系统通知权限已开启\n\n" +
"模块无需保持在后台运行,到达同步时间后会自动启动,如果到达时间后模块正在运行则会自动取消本次计划任务。"
confirmButton(text = "保存设置") {
notifyIconAutoSyncTime = it
binding.notifyIconAutoSyncText.text = it
modulePrefs.put(DataConst.NOTIFY_ICON_FIX_AUTO_TIME, it)
SystemUITool.refreshSystemUI(context, isRefreshCacheOnly = true)
}
cancelButton()
noCancelable()
}
}
}
/** 重启按钮点击事件 */
binding.titleRestartIcon.setOnClickListener { SystemUITool.restartSystemUI(context = this) }
/** 项目地址按钮点击事件 */
binding.titleGithubIcon.setOnClickListener { openBrowser(url = "https://github.com/fankes/ColorOSNotifyIcon") }
/** 恰饭! */
binding.linkWithFollowMe.setOnClickListener {
openBrowser(url = "https://www.coolapk.com/u/876977", packageName = "com.coolapk.market")
}
}
/** 刷新模块状态 */
private fun refreshModuleStatus() {
binding.mainLinStatus.setBackgroundResource(
when {
isXposedModuleActive && (isModuleRegular.not() || isModuleValied.not()) -> R.drawable.bg_yellow_round
isXposedModuleActive -> R.drawable.bg_green_round
else -> R.drawable.bg_dark_round
}
)
binding.mainImgStatus.setImageResource(
when {
isXposedModuleActive -> R.mipmap.ic_success
else -> R.mipmap.ic_warn
}
)
binding.mainTextStatus.text =
when {
isXposedModuleActive && isModuleRegular.not() && modulePrefs.get(DataConst.ENABLE_MODULE).not() -> "模块已停用"
isXposedModuleActive && isModuleRegular.not() -> "模块已激活,请重启系统界面"
isXposedModuleActive && isModuleValied.not() -> "模块已更新,请重启系统界面"
isXposedModuleActive -> "模块已激活"
else -> "模块未激活"
}
binding.mainTextApiWay.isVisible = isXposedModuleActive
binding.mainTextApiWay.text = "Activated by ${YukiHookModuleStatus.executorName} API ${YukiHookModuleStatus.executorVersion}"
}
override fun onResume() {
super.onResume()
/** 刷新模块状态 */
refreshModuleStatus()
/** 发送广播检查模块激活状态 */
SystemUITool.checkingActivated(context = this) { isRegular, isValied ->
isModuleRegular = isRegular
isModuleValied = isValied
refreshModuleStatus()
}
}
}

View File

@@ -0,0 +1,69 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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/26.
*/
@file:Suppress("DEPRECATION")
package com.fankes.coloros.notify.ui.activity.auto
import android.app.Activity
import android.os.Bundle
import android.view.View
import android.view.WindowManager
import com.fankes.coloros.notify.ui.activity.base.BaseActivity
import com.fankes.coloros.notify.utils.factory.delayedRun
import com.fankes.coloros.notify.utils.tool.IconRuleManagerTool
import com.fankes.coloros.notify.utils.tool.SystemUITool
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication.Companion.appContext
class NotifyIconRuleUpdateActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
/** 设置透明窗口 */
window?.decorView?.systemUiVisibility =
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
window?.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
window?.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
/** 注册 */
SystemUITool.register(context = this)
/** 检测运行状态 */
if (BaseActivity.isMainThreadRunning) {
finish()
return
}
/** 拉取云端数据 */
IconRuleManagerTool.sync(appContext) {
/** 刷新系统界面 */
SystemUITool.refreshSystemUI()
/** 结束当前窗口 */
runOnUiThread { delayedRun(ms = 1000) { finish() } }
}
/** 切换到后台 */
moveTaskToBack(true)
}
override fun onDestroy() {
super.onDestroy()
/** 解除注册 */
SystemUITool.unregister(context = this)
}
}

View File

@@ -20,18 +20,44 @@
*
* This file is Created by fankes on 2022/1/30.
*/
package com.fankes.coloros.notify.ui.base
@file:Suppress("UNCHECKED_CAST")
package com.fankes.coloros.notify.ui.activity.base
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.viewbinding.ViewBinding
import com.fankes.coloros.notify.R
import com.fankes.coloros.notify.utils.isNotSystemInDarkMode
import com.fankes.coloros.notify.utils.factory.isNotSystemInDarkMode
import com.fankes.coloros.notify.utils.tool.SystemUITool
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 : AppCompatActivity() {
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
companion object {
/** 应用是否正在运行 */
var isMainThreadRunning = false
}
/** 获取绑定布局对象 */
lateinit var binding: VB
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
isMainThreadRunning = true
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()
/** 初始化沉浸状态栏 */
@@ -43,5 +69,18 @@ abstract class BaseActivity : AppCompatActivity() {
navigationBarDarkIcon(isNotSystemInDarkMode)
fitsSystemWindows(true)
}
/** 注册 */
SystemUITool.register(context = this)
/** 装载子类 */
onCreate()
}
/** 回调 [onCreate] 方法 */
abstract fun onCreate()
override fun onDestroy() {
super.onDestroy()
/** 解除注册 */
SystemUITool.unregister(context = this)
}
}

View File

@@ -22,15 +22,17 @@
*/
@file:Suppress("SameParameterValue")
package com.fankes.coloros.notify.view
package com.fankes.coloros.notify.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.coloros.notify.utils.dp
import com.fankes.coloros.notify.utils.drawable.drawabletoolbox.DrawableBuilder
import com.fankes.coloros.notify.utils.factory.dp
import com.fankes.coloros.notify.utils.factory.isSystemInDarkMode
class MaterialSwitch(context: Context, attrs: AttributeSet?) : SwitchCompat(context, attrs) {
@@ -43,27 +45,31 @@ class MaterialSwitch(context: Context, attrs: AttributeSet?) : SwitchCompat(cont
return ColorStateList(states, colors)
}
private val thumbColor get() = if (context.isSystemInDarkMode) 0xFF7C7C7C else 0xFFCCCCCC
init {
trackDrawable = DrawableBuilder()
.rectangle()
.rounded()
.solidColor(0xFF656565.toInt())
.height(20.dp)
.cornerRadius(15.dp)
.height(20.dp(context))
.cornerRadius(15.dp(context))
.build()
thumbDrawable = DrawableBuilder()
.rectangle()
.rounded()
.solidColor(Color.WHITE)
.size(20.dp, 20.dp)
.cornerRadius(20.dp)
.strokeWidth(2.dp)
.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()
thumbColor.toInt(),
thumbColor.toInt()
)
isSingleLine = true
ellipsize = TextUtils.TruncateAt.END
}
}

View File

@@ -1,146 +0,0 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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/25.
*/
@file:Suppress("TrustAllX509TrustManager", "CustomX509TrustManager", "DEPRECATION")
package com.fankes.coloros.notify.utils
import android.app.Activity
import android.app.ProgressDialog
import android.content.Intent
import android.net.Uri
import android.provider.Settings
import com.highcapable.yukihookapi.hook.log.loggerD
import okhttp3.*
import java.io.IOException
import java.security.SecureRandom
import java.security.cert.X509Certificate
import javax.net.ssl.*
/**
* 网络请求管理类
*/
object ClientRequestTool {
/**
* 检查网络连接情况
* @param context 实例
* @param it 已连接回调
*/
fun checkingInternetConnect(context: Activity, it: () -> Unit) =
ProgressDialog(context).apply {
setDefaultStyle(context)
setCancelable(false)
setTitle("准备中")
setMessage("正在检查网络连接情况")
}.apply {
wait(context, url = "https://www.baidu.com") { isDone, _ ->
cancel()
if (isDone) it() else
context.showDialog {
title = "网络不可用"
msg = "无法连接到互联网,请检查你当前的设备是否可以上网,且没有在手机管家中禁用本模块的联网权限。"
confirmButton(text = "检查设置") {
runCatching {
context.startActivity(Intent().apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
data = Uri.fromParts("package", context.packageName, null)
})
}.onFailure { context.snake(msg = "启动应用信息页面失败") }
}
cancelButton()
}
}
}.show()
/**
* 发送 GET 请求内容并等待
* @param context 实例
* @param url 请求地址
* @param it 回调 - ([Boolean] 是否成功,[String] 成功的内容或失败消息)
*/
fun wait(context: Activity, url: String, it: (Boolean, String) -> Unit) = runCatching {
OkHttpClient().newBuilder().apply {
SSLSocketClient.sSLSocketFactory?.let { sslSocketFactory(it, SSLSocketClient.trustManager) }
hostnameVerifier(SSLSocketClient.hostnameVerifier)
}.build().newCall(
Request.Builder()
.url(url)
.get()
.build()
).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
context.runOnUiThread { it(false, e.toString()) }
}
override fun onResponse(call: Call, response: Response) {
val bodyString = response.body?.string() ?: ""
context.runOnUiThread { it(true, bodyString) }
}
})
}.onFailure { it(false, "URL 无效") }
/**
* 自动信任 SSL 证书
*
* 放行全部加密 SSL 请求
*/
object SSLSocketClient {
/**
* 格式化实例
* @return [SSLSocketFactory] or null
*/
val sSLSocketFactory
get() = safeOfNull {
SSLContext.getInstance("TLS").let {
it.init(null, arrayOf<TrustManager>(trustManager), SecureRandom())
it.socketFactory
}
}
/**
* 使用的实例
* @return [HostnameVerifier]
*/
val hostnameVerifier get() = HostnameVerifier { _, _ -> true }
/**
* 信任管理者
* @return [X509TrustManager]
*/
val trustManager
get() = object : X509TrustManager {
override fun checkClientTrusted(chain: Array<X509Certificate?>?, authType: String?) {
loggerD(msg = "TrustX509 --> $authType")
}
override fun checkServerTrusted(chain: Array<X509Certificate?>?, authType: String?) {
loggerD(msg = "TrustX509 --> $authType")
}
override fun getAcceptedIssuers() = arrayOf<X509Certificate>()
}
}
}

View File

@@ -1,113 +0,0 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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", "DEPRECATION")
package com.fankes.coloros.notify.utils
import android.app.AlertDialog
import android.content.Context
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager
import kotlin.math.round
/**
* 构造对话框
* @param it 对话框方法体
*/
fun Context.showDialog(it: DialogBuilder.() -> Unit) = DialogBuilder(this).apply(it).show()
/**
* 对话框构造器
* @param context 实例
*/
class DialogBuilder(private val context: Context) {
private var instance: AlertDialog.Builder? = null // 实例对象
private var customLayoutView: View? = null // 自定义布局
init {
instance = AlertDialog.Builder(context, android.R.style.Theme_Material_Light_Dialog)
}
/** 设置对话框不可关闭 */
fun noCancelable() = instance?.setCancelable(false)
/** 设置对话框标题 */
var title
get() = ""
set(value) {
instance?.setTitle(value)
}
/** 设置对话框消息内容 */
var msg
get() = ""
set(value) {
instance?.setMessage(value)
}
/**
* 设置对话框自定义布局
* @param resId 属性资源 Id
* @return [View]
*/
fun addView(resId: Int): View {
customLayoutView = LayoutInflater.from(context).inflate(resId, null)
return customLayoutView ?: error("Inflate $resId failed")
}
/**
* 设置对话框确定按钮
* @param text 按钮文本内容
* @param it 点击事件
*/
fun confirmButton(text: String = "确定", it: () -> Unit = {}) =
instance?.setPositiveButton(text) { _, _ -> it() }
/**
* 设置对话框取消按钮
* @param text 按钮文本内容
* @param it 点击事件
*/
fun cancelButton(text: String = "取消", it: () -> Unit = {}) =
instance?.setNegativeButton(text) { _, _ -> it() }
/**
* 设置对话框第三个按钮
* @param text 按钮文本内容
* @param it 点击事件
*/
fun neutralButton(text: String = "更多", it: () -> Unit = {}) =
instance?.setNeutralButton(text) { _, _ -> it() }
/** 显示对话框 */
internal fun show() = instance?.create()?.apply {
val dm = DisplayMetrics()
(context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay.getMetrics(dm)
customLayoutView?.let { setView(it.apply { minimumWidth = round(x = dm.widthPixels / 1.3).toInt() }) }
setDefaultStyle(context = this@DialogBuilder.context)
}?.show()
}

View File

@@ -1,69 +0,0 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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/8.
*/
package com.fankes.coloros.notify.utils
import android.content.Context
import com.google.android.material.snackbar.Snackbar
import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus
/**
* 系统界面工具
*/
object SystemUITool {
/**
* 重启系统界面
* @param context 实例
*/
fun restartSystemUI(context: Context) =
context.showDialog {
title = "重启系统界面"
msg = "你确定要立即重启系统界面吗?"
confirmButton {
execShellSu(cmd = "pgrep systemui").also { pid ->
if (pid.isNotBlank())
execShellSu(cmd = "kill -9 $pid")
else toast(msg = "ROOT 权限获取失败")
}
}
cancelButton()
}
/**
* 显示需要重启系统界面的 [Snackbar]
* @param context 实例
*/
fun showNeedRestartSnake(context: Context) =
if (YukiHookModuleStatus.isActive())
context.snake(msg = "设置需要重启系统界面才能生效", actionText = "立即重启") { restartSystemUI(context) }
else context.snake(msg = "模块没有激活,更改不会生效")
/**
* 显示更新数据后需要重启系统界面的 [Snackbar]
* @param context 实例
*/
fun showNeedUpdateApplySnake(context: Context) =
if (YukiHookModuleStatus.isActive())
context.snake(msg = "数据已更新,请重启系统界面使更改生效", actionText = "立即重启") { restartSystemUI(context) }
else context.snake(msg = "模块没有激活,更改不会生效")
}

View File

@@ -1,384 +0,0 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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", "PrivateApi", "unused", "ObsoleteSdkInt")
package com.fankes.coloros.notify.utils
import android.app.Activity
import android.app.AlertDialog
import android.content.ClipData
import android.content.ClipboardManager
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.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.drawable.GradientDrawable
import android.net.Uri
import android.os.Build
import android.util.Base64
import android.widget.Toast
import com.fankes.coloros.notify.application.CNNApplication.Companion.appContext
import com.google.android.material.snackbar.Snackbar
import com.highcapable.yukihookapi.hook.factory.classOf
import com.highcapable.yukihookapi.hook.factory.field
import com.highcapable.yukihookapi.hook.factory.hasClass
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.log.loggerE
import com.highcapable.yukihookapi.hook.type.java.StringType
import com.topjohnwu.superuser.Shell
import java.io.ByteArrayOutputStream
/**
* 系统深色模式是否开启
* @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 [Boolean] 是否开启
*/
val Context.isSystemInDarkMode get() = (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
/**
* 系统深色模式是否没开启
* @return [Boolean] 是否开启
*/
inline val Context.isNotSystemInDarkMode get() = !isSystemInDarkMode
/**
* 系统版本是否高于或等于 Android 12
* @return [Boolean] 是否符合条件
*/
inline val isUpperOfAndroidS get() = Build.VERSION.SDK_INT > Build.VERSION_CODES.R
/**
* 系统版本是否低于 Android 9
* @return [Boolean] 是否符合条件
*/
inline val isLowerAndroidP get() = Build.VERSION.SDK_INT < Build.VERSION_CODES.P
/**
* 当前设备是否是 ColorOS 定制 Android 系统
* @return [Boolean] 是否符合条件
*/
val isColorOS by lazy { ("oppo.R").hasClass || ("com.color.os.ColorBuild").hasClass || ("oplus.R").hasClass }
/**
* 当前设备是否不是 ColorOS 定制 Android 系统
* @return [Boolean] 是否符合条件
*/
inline val isNotColorOS get() = !isColorOS
/**
* 获取 ColorOS 版本
* @return [String]
*/
val colorOSVersion
get() = safeOf(default = "无法获取") {
(classOf(name = "com.oplus.os.OplusBuild").let {
it.field { name = "VERSIONS" }.ignoredError().of<Array<String>>()
?.get((it.method { name = "getOplusOSVERSION" }.ignoredError().get().invoke<Int>() ?: 23) - 1)
} ?: findPropString(key = "ro.system.build.fingerprint", default = "无法获取")
.split("ssi:")[1]
.split("/")[0].trim()) + " ${Build.DISPLAY}"
}
/**
* 得到安装包信息
* @return [PackageInfo]
*/
val Context.packageInfo get() = packageManager?.getPackageInfo(packageName, 0) ?: PackageInfo()
/**
* 判断应用是否安装
* @return [Boolean]
*/
val String.isInstall
get() = safeOfFalse {
appContext.packageManager.getPackageInfo(
this,
PackageManager.GET_UNINSTALLED_PACKAGES
)
true
}
/**
* 得到版本信息
* @return [String]
*/
val Context.versionName get() = packageInfo.versionName ?: ""
/**
* 得到版本号
* @return [Int]
*/
val Context.versionCode get() = packageInfo.versionCode
/**
* dp 转换为 px
* @return [Int]
*/
val Number.dp get() = (toFloat() * appContext.resources.displayMetrics.density).toInt()
/**
* dp 转换为 px
* @param context 使用的实例
* @return [Int]
*/
fun Number.dp(context: Context) = (toFloat() * context.resources.displayMetrics.density).toInt()
/**
* Base64 加密
* @return [String]
*/
val Bitmap.base64
get() = safeOfNothing {
val baos = ByteArrayOutputStream()
compress(Bitmap.CompressFormat.PNG, 100, baos)
Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT)
}
/**
* Base64 加密
* @return [String]
*/
val String.base64: String get() = Base64.encodeToString(toByteArray(), Base64.DEFAULT)
/**
* Base64 解密为字节流
* @return [ByteArray]
*/
val String.unbase64 get() = Base64.decode(this, Base64.DEFAULT) ?: ByteArray(0)
/**
* 字节流解析为位图
* @return [Bitmap]
*/
val ByteArray.bitmap: Bitmap get() = BitmapFactory.decodeByteArray(this, 0, size)
/**
* 字符串解析为位图
* @return [Bitmap]
*/
val String.bitmap: Bitmap get() = unbase64.bitmap
/**
* 设置对话框默认风格
* @param context 使用的实例
*/
fun AlertDialog.setDefaultStyle(context: Context) =
window?.setBackgroundDrawable(
GradientDrawable(
GradientDrawable.Orientation.TOP_BOTTOM,
intArrayOf(Color.WHITE, Color.WHITE)
).apply {
shape = GradientDrawable.RECTANGLE
gradientType = GradientDrawable.LINEAR_GRADIENT
cornerRadius = 15.dp(context).toFloat()
})
/**
* 获取系统 Prop 值
* @param key Key
* @param default 默认值
* @return [String]
*/
fun findPropString(key: String, default: String = "") = safeOf(default) {
(classOf(name = "android.os.SystemProperties").method {
name = "get"
param(StringType, StringType)
}.get().invoke(key, default)) ?: default
}
/**
* 执行命令 - su
* @param cmd 命令
* @return [String] 执行结果
*/
fun execShellSu(cmd: String) = safeOfNothing {
Shell.su(cmd).exec().out.let {
if (it.isNotEmpty()) it[0].trim() else ""
}
}
/**
* 弹出 [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()
/**
* 启动系统浏览器
* @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 = "启动系统浏览器失败")
}
/**
* 复制到剪贴板
* @param content 要复制的文本
*/
fun Context.copyToClipboard(content: String) = runCatching {
(getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).apply {
setPrimaryClip(ClipData.newPlainText(null, content))
(primaryClip?.getItemAt(0)?.text ?: "").also {
if (it != content) snake(msg = "复制失败") else snake(msg = "已复制")
}
}
}
/**
* 忽略异常返回值
* @param it 回调 - 如果异常为空
* @return [T] 发生异常时返回设定值否则返回正常值
*/
inline fun <T> safeOfNull(it: () -> T): T? = safeOf(null, it)
/**
* 忽略异常返回值
* @param it 回调 - 如果异常为 false
* @return [Boolean] 发生异常时返回设定值否则返回正常值
*/
inline fun safeOfFalse(it: () -> Boolean) = safeOf(default = false, it)
/**
* 忽略异常返回值
* @param it 回调 - 如果异常为 true
* @return [Boolean] 发生异常时返回设定值否则返回正常值
*/
inline fun safeOfTrue(it: () -> Boolean) = safeOf(default = true, it)
/**
* 忽略异常返回值
* @param it 回调 - 如果异常为 false
* @return [String] 发生异常时返回设定值否则返回正常值
*/
inline fun safeOfNothing(it: () -> String) = safeOf(default = "", it)
/**
* 忽略异常返回值
* @param it 回调 - 如果异常为 false
* @return [Int] 发生异常时返回设定值否则返回正常值
*/
inline fun safeOfNan(it: () -> Int) = safeOf(default = 0, it)
/**
* 忽略异常返回值
* @param default 异常返回值
* @param it 正常回调值
* @return [T] 发生异常时返回设定值否则返回正常值
*/
inline fun <T> safeOf(default: T, it: () -> T): T {
return try {
it()
} catch (t: NullPointerException) {
default
} catch (t: UnsatisfiedLinkError) {
default
} catch (t: UnsupportedOperationException) {
default
} catch (t: ClassNotFoundException) {
default
} catch (t: IllegalStateException) {
default
} catch (t: NoSuchMethodError) {
default
} catch (t: NoSuchFieldError) {
default
} catch (t: Error) {
default
} catch (t: Exception) {
default
} catch (t: Throwable) {
default
}
}
/**
* 忽略异常运行
* @param msg 出错输出的消息 - 默认为空
* @param it 正常回调
*/
inline fun safeRun(msg: String = "", it: () -> Unit) {
try {
it()
} catch (e: NullPointerException) {
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
} catch (e: UnsatisfiedLinkError) {
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
} catch (e: UnsupportedOperationException) {
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
} catch (e: ClassNotFoundException) {
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
} catch (e: IllegalStateException) {
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
} catch (e: NoSuchMethodError) {
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
} catch (e: NoSuchFieldError) {
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
} catch (e: Error) {
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
} catch (e: Exception) {
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
} catch (e: Throwable) {
}
}

View File

@@ -0,0 +1,209 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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.coloros.notify.utils.factory
import android.app.Dialog
import android.app.TimePickerDialog
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.CauseProblemsApi
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 timeSet 当前时间 - 不写将使用当前时间格式HH:mm
* @param it 回调 - 小时与分钟 HH:mm
*/
fun Context.showTimePicker(timeSet: String = "", it: (String) -> Unit) =
TimePickerDialog(this, { _, h, m -> it("${h.autoZero}:${m.autoZero}") }, timeSet.hour, timeSet.minute, true).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 // 对话框实例
@CauseProblemsApi
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,83 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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.coloros.notify.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,451 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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", "PrivateApi", "unused", "ObsoleteSdkInt")
package com.fankes.coloros.notify.utils.factory
import android.app.Activity
import android.app.Notification
import android.app.Service
import android.app.WallpaperManager
import android.app.WallpaperManager.FLAG_SYSTEM
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.net.ConnectivityManager
import android.net.Uri
import android.os.Build
import android.os.Handler
import android.provider.Settings
import android.util.Base64
import android.widget.Toast
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.getSystemService
import com.google.android.material.snackbar.Snackbar
import com.highcapable.yukihookapi.hook.factory.classOf
import com.highcapable.yukihookapi.hook.factory.field
import com.highcapable.yukihookapi.hook.factory.hasClass
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.type.java.StringType
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication.Companion.appContext
import com.topjohnwu.superuser.Shell
import java.io.ByteArrayOutputStream
import java.text.SimpleDateFormat
import java.util.*
/**
* 系统深色模式是否开启
* @return [Boolean] 是否开启
*/
val isSystemInDarkMode get() = appContext.isSystemInDarkMode
/**
* 系统深色模式是否没开启
* @return [Boolean] 是否开启
*/
inline val isNotSystemInDarkMode get() = !isSystemInDarkMode
/**
* 系统深色模式是否开启
* @return [Boolean] 是否开启
*/
val Context.isSystemInDarkMode get() = (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
/**
* 系统深色模式是否没开启
* @return [Boolean] 是否开启
*/
inline val Context.isNotSystemInDarkMode get() = !isSystemInDarkMode
/**
* 系统版本是否高于或等于 Android 12
* @return [Boolean] 是否符合条件
*/
inline val isUpperOfAndroidS get() = Build.VERSION.SDK_INT > Build.VERSION_CODES.R
/**
* 系统版本是否低于 Android 9
* @return [Boolean] 是否符合条件
*/
inline val isLowerAndroidP get() = Build.VERSION.SDK_INT < Build.VERSION_CODES.P
/**
* 当前设备是否是 ColorOS 定制 Android 系统
* @return [Boolean] 是否符合条件
*/
val isColorOS by lazy { ("oppo.R").hasClass || ("com.color.os.ColorBuild").hasClass || ("oplus.R").hasClass }
/**
* 当前设备是否不是 ColorOS 定制 Android 系统
* @return [Boolean] 是否符合条件
*/
inline val isNotColorOS get() = !isColorOS
/**
* 当前设备是否为 RealmeUI
* @return [Boolean] 是否符合条件
*/
val isRealmeUI
get() = safeOfFalse {
val query = appContext.contentResolver.query(
Uri.parse("content://com.oplus.customize.coreapp.configmanager.configprovider.AppFeatureProvider")
.buildUpon()
.appendPath("app_feature")
.build(), null, "featurename=?", arrayOf("com.android.launcher.device_rm"), null
)
val isExist = query != null && query.count > 0
query?.close()
isExist
}
/**
* 获取 ColorOS 完整版本
* @return [String]
*/
val colorOSFullVersion get() = "${if (isRealmeUI) "RealmeUI " else ""}$colorOSVersion"
/**
* 获取 ColorOS 版本
* @return [String]
*/
val colorOSVersion
get() = safeOf(default = "无法获取") {
(classOf(name = "com.oplus.os.OplusBuild").let {
it.field { name = "VERSIONS" }.ignoredError().get().array<String>().takeIf { e -> e.isNotEmpty() }
?.get(it.method { name = "getOplusOSVERSION" }.ignoredError().get().int() - 1)
} ?: findPropString(
key = "ro.system.build.fingerprint", default = "无法获取"
).split("ssi:")[1].split("/")[0].trim()) + " ${Build.DISPLAY}"
}
/**
* 得到安装包信息
* @return [PackageInfo]
*/
val Context.packageInfo get() = packageManager?.getPackageInfo(packageName, 0) ?: PackageInfo()
/**
* 判断应用是否安装
* @return [Boolean]
*/
val String.isInstall
get() = safeOfFalse {
appContext.packageManager.getPackageInfo(
this, PackageManager.GET_UNINSTALLED_PACKAGES
)
true
}
/**
* 得到版本信息
* @return [String]
*/
val Context.versionName get() = packageInfo.versionName ?: ""
/**
* 得到版本号
* @return [Int]
*/
val Context.versionCode get() = packageInfo.versionCode
/**
* 得到 APP 名称
* @param name APP 包名
* @return [String]
*/
fun Context.findAppName(name: String) =
safeOfNothing { packageManager?.getPackageInfo(name, 0)?.applicationInfo?.loadLabel(packageManager).toString() }
/**
* 得到 APP 图标
* @param name APP 包名
* @return [Drawable] or null
*/
fun Context.findAppIcon(name: String) =
safeOfNull { packageManager?.getPackageInfo(name, 0)?.applicationInfo?.loadIcon(packageManager) }
/**
* 获取 APP 是否为 DEBUG 版本
* @param name APP 包名
* @return [Boolean]
*/
fun Context.isAppDebuggable(name: String) =
safeOfFalse { (packageManager?.getPackageInfo(name, 0)?.applicationInfo?.flags?.and(ApplicationInfo.FLAG_DEBUGGABLE) ?: 0) != 0 }
/**
* 对数值自动补零
* @return [String]
*/
val Int.autoZero: String get() = if (this < 10) "0$this" else toString()
/**
* 从字符串获取小时
* @return [Int]
*/
val String.hour
get() = safeOfNan {
Calendar.getInstance().also {
it.time = SimpleDateFormat("HH:mm", Locale.CHINA).parse(this) as Date
}.get(Calendar.HOUR_OF_DAY)
}
/**
* 从字符串获取分钟
* @return [Int]
*/
val String.minute
get() = safeOfNan {
Calendar.getInstance().also {
it.time = SimpleDateFormat("HH:mm", Locale.CHINA).parse(this) as Date
}.get(Calendar.MINUTE)
}
/**
* 是否关闭了通知权限
* @return [Boolean]
*/
val isNotNoificationEnabled get() = !NotificationManagerCompat.from(appContext).areNotificationsEnabled()
/**
* 网络连接是否正常
* @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
/**
* 获取系统壁纸颜色
*
* 由于 ColorOS 阉割了 [android.R.color.system_accent1_600] 这里取系统壁纸颜色做补偿
* @return [Int] 无法获取时返回透明色
*/
val Context.wallpaperColor
get() = safeOfNan {
WallpaperManager.getInstance(this).getWallpaperColors(FLAG_SYSTEM)?.secondaryColor?.toArgb() ?: 0
}
/**
* 是否为白色
* @return [Boolean]
*/
val Int.isWhite
get() = safeOfTrue {
val r = this and 0xff0000 shr 16
val g = this and 0x00ff00 shr 8
val b = this and 0x0000ff
(0.2126 * r + 0.7152 * g + 0.0722 * b) >= 128
}
/**
* Base64 加密
* @return [String]
*/
val Bitmap.base64
get() = safeOfNothing {
val baos = ByteArrayOutputStream()
compress(Bitmap.CompressFormat.PNG, 100, baos)
Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT)
}
/**
* Base64 加密
* @return [String]
*/
val String.base64: String get() = Base64.encodeToString(toByteArray(), Base64.DEFAULT)
/**
* Base64 解密为字节流
* @return [ByteArray]
*/
val String.unbase64 get() = Base64.decode(this, Base64.DEFAULT) ?: ByteArray(0)
/**
* 字节流解析为位图
* @return [Bitmap]
*/
val ByteArray.bitmap: Bitmap get() = BitmapFactory.decodeByteArray(this, 0, size)
/**
* 字符串解析为位图
* @return [Bitmap]
*/
val String.bitmap: Bitmap get() = unbase64.bitmap
/**
* 获取系统 Prop 值
* @param key Key
* @param default 默认值
* @return [String]
*/
fun findPropString(key: String, default: String = "") = safeOf(default) {
(classOf(name = "android.os.SystemProperties").method {
name = "get"
param(StringType, StringType)
}.get().invoke(key, default)) ?: default
}
/**
* 是否有 Root 权限
* @return [Boolean]
*/
val isRootAccess get() = safeOfFalse { Shell.rootAccess() }
/**
* 执行命令
* @param cmd 命令
* @param isSu 是否使用 Root 权限执行 - 默认:是
* @return [String] 执行结果
*/
fun execShell(cmd: String, isSu: Boolean = true) = safeOfNothing {
(if (isSu) Shell.su(cmd) else Shell.sh(cmd)).exec().out.let {
if (it.isNotEmpty()) it[0].trim() else ""
}
}
/**
* 弹出 [Toast]
* @param msg 提示内容
*/
fun toast(msg: String) = Toast.makeText(appContext, msg, Toast.LENGTH_SHORT).show()
/**
* 跳转到指定页面
*
* [T] 为指定的 [Activity]
*/
inline fun <reified T : Activity> Context.navigate() = runInSafe { startActivity(Intent(this, T::class.java)) }
/**
* 跳转到指定页面
*
* [T] 为指定的 [Activity]
*/
inline fun <reified T : Activity> Service.navigate() =
runInSafe { startActivity(Intent(this, T::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }) }
/**
* 弹出 [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(if (isSystemInDarkMode) Color.BLACK else Color.WHITE)
setAction(actionText) { it() }
}.show()
/**
* 启动系统浏览器
* @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 = "启动系统浏览器失败")
}
/**
* 跳转 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 应用信息失败") }
/** 跳转通知设置界面 */
fun Context.openNotifySetting() = runCatching {
Intent().also { intent ->
intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS
intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
intent.putExtra(Notification.EXTRA_CHANNEL_ID, applicationInfo.uid)
startActivity(intent)
}
}.onFailure { snake(msg = "跳转通知设置失败") }
/**
* 复制到剪贴板
* @param content 要复制的文本
*/
fun Context.copyToClipboard(content: String) = runInSafe {
(getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).apply {
setPrimaryClip(ClipData.newPlainText(null, content))
(primaryClip?.getItemAt(0)?.text ?: "").also {
if (it != content) snake(msg = "复制失败") else snake(msg = "已复制")
}
}
}
/**
* 时间戳 -> 时间
* @param format 格式化方法 - 默认yyyy-MM-dd HH:mm:ss
* @return [String] 目标字符串时间
*/
fun Long.stampToDate(format: String = "yyyy-MM-dd HH:mm:ss") =
safeOfNothing { SimpleDateFormat(format, Locale.CHINA).format(Date(this)) ?: "" }
/**
* 延迟执行
* @param ms 毫秒 - 默认150
* @param it 方法体
*/
fun Any?.delayedRun(ms: Long = 150, it: () -> Unit) = runInSafe { Handler().postDelayed({ it() }, ms) }

View File

@@ -0,0 +1,128 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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.coloros.notify.utils.tool
import android.app.Activity
import android.content.Context
import com.fankes.coloros.notify.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 = "ColorOSNotifyIcon"
/**
* 获取最新版本信息
* @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,189 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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.coloros.notify.utils.tool
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.Icon
import android.os.Build
import androidx.core.graphics.drawable.toBitmap
import com.fankes.coloros.notify.const.Const
import com.fankes.coloros.notify.hook.HookEntry
import com.fankes.coloros.notify.utils.factory.*
/**
* 通知图标适配推送通知类
*
* 这个类需要在 [HookEntry] 中调用
*/
object IconAdaptationTool {
/** 推送通知的渠道名称 */
private const val NOTIFY_CHANNEL = "notifyRuleSupportId"
/** 已过期的日期 */
private val outDateLimits = HashSet<String>()
/**
* 使用的小图标
* @return [Bitmap]
*/
private val smallIcon by lazy {
("iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAG\n" +
"x2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkP\n" +
"SJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG\n" +
"9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNi4wLWMwMDYgNzkuZGF\n" +
"iYWNiYiwgMjAyMS8wNC8xNC0wMDozOTo0NCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6\n" +
"cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gP\n" +
"HJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYW\n" +
"RvYmUuY29tL3hhcC8xLjAvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1\n" +
"lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90\n" +
"b3Nob3AvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuM\n" +
"C9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cG\n" +
"UvUmVzb3VyY2VFdmVudCMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI\n" +
"yLjQgKE1hY2ludG9zaCkiIHhtcDpDcmVhdGVEYXRlPSIyMDIyLTAzLTIzVDAyOjU1OjM2\n" +
"KzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMi0wMy0yM1QwMzowMDoyMSswODowMCIge\n" +
"G1wOk1ldGFkYXRhRGF0ZT0iMjAyMi0wMy0yM1QwMzowMDoyMSswODowMCIgZGM6Zm9ybW\n" +
"F0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0N\n" +
"Qcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlp\n" +
"ZDpiZGY1YWY2NS1lYjYxLTRiOGUtOTk1NS01OTE5OThkN2ExNDAiIHhtcE1NOkRvY3VtZ\n" +
"W50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDo2MDFlYjQ1OC01ZTdlLWFjNDYtODA0Mi\n" +
"1iNWJmYTFhYWQwNGMiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpjYTU\n" +
"4YTA1ZS04OTU1LTQyNzItODg2NC0xNmI5MWI0YzcxMmUiPiA8eG1wTU06SGlzdG9yeT4g\n" +
"PHJkZjpTZXE+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJjcmVhdGVkIiBzdEV2dDppbnN0Y\n" +
"W5jZUlEPSJ4bXAuaWlkOmNhNThhMDVlLTg5NTUtNDI3Mi04ODY0LTE2YjkxYjRjNzEyZS\n" +
"Igc3RFdnQ6d2hlbj0iMjAyMi0wMy0yM1QwMjo1NTozNiswODowMCIgc3RFdnQ6c29mdHd\n" +
"hcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDIyLjQgKE1hY2ludG9zaCkiLz4gPHJkZjps\n" +
"aSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjg5Y\n" +
"mIxNTI1LWVjYmMtNGY2NC1iZTRlLWU1N2EyNjQ1NDE1YyIgc3RFdnQ6d2hlbj0iMjAyMi\n" +
"0wMy0yM1QwMjo1NzoxMCswODowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGh\n" +
"vdG9zaG9wIDIyLjQgKE1hY2ludG9zaCkiIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4gPHJkZjps\n" +
"aSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmJkZ\n" +
"jVhZjY1LWViNjEtNGI4ZS05OTU1LTU5MTk5OGQ3YTE0MCIgc3RFdnQ6d2hlbj0iMjAyMi\n" +
"0wMy0yM1QwMzowMDoyMSswODowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGh\n" +
"vdG9zaG9wIDIyLjQgKE1hY2ludG9zaCkiIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4gPC9yZGY6\n" +
"U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+I\n" +
"DwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+QdGsggAAA35JREFUaIHtmE2IVW\n" +
"Ucxn9z0Ukm7cMSo1AxUwjNIclLRVSL0CAoyD5oYwS1iogWGW0rwQpcJNjCRdKmTR+SBhF\n" +
"BWWZlZllmOgsT/EqHKRuqMVv8XJy5zPGd95x75hyh03Ae+C/O//2/z3Oee97P26MyGdD6\n" +
"r1/gQqExUjc0RuqGxkjd0BipGxojdcOkMTKlS/tCYBXQBvqBWcA/wI/AD8AW4NML9C79w\n" +
"APAYmAOcCUwCBwCvgTeB37J7K3Goq1utRgG1NUZPEXibnVnQa231CUxnhjxmoKkId5WWx\n" +
"M08XpJrSe7GXmlJHEH36l9oUgketRPKmqtS3P2OHaxWgO8HBl9vwPbgM+AU8B0YAnJ3Fk\n" +
"Uqd8HLAfO5MyHz4HbIvkh4ENgJ/Dn6DxZCdxFfGF6GngtPUeuz3D9qnpZzi/7mDoS6fe9\n" +
"Oi1SP0XdnqH1vDo9Q2eeujmj33WmhtbuSMFTOQbSsUg9Gem/3/HDbEuk7ox6c0GtZyP9d\n" +
"3WMLIg0vlGQuBPz1cEIzx51aqouxN/qsglqvRnhWYj6UpAcVnsnSN75/MciIntTfC+m8r\n" +
"+qi0vo9Kp/BBrrUD8KkutLkHdijvpbxMy3jn2Z1eoL6uUVdDYE/NtRjwfJ+yoIoF6rnoi\n" +
"Y2VORNx33B9wHWsDMYEk7m7NsFsEhYBlwMsjfCOwFplbkBzgdPPe2gOEg2e38VQQnSMwM\n" +
"BvmlwBfAtIr8FwXPIy1gf5BsVxS5ieTQd5zEzFDQvhzYBfRU0OgPnk/j+GPJYMlx26d+P\n" +
"MpxNjXX5hlfmneYbJBltAYCrk2YnCZDnHeOKRAXqz8FHMOp9mtMltsQu0uYeTzC085yqL\n" +
"qqIPFMdV+k/46gbr7xpflrz98086Kt/hv0P6JjR5RbIwKqz5h/NF/p+OVb9bA6O1I/N6P\n" +
"+Z/WWLiYezXjHO9JGMBlOMRxW16r3mhwn7jS5D2Rdho6ps3JeKGuYaXKnedhkL5qhLlWf\n" +
"UL/KqN/Y4Q1FNmd0KIqj6tU5JtJmjnThGu7S/k6aMyayvpSF5FfL+xJhXKF+U1JrU8iXJ\n" +
"bLC7M8Z4pTJXJrIypOO59ShgloH1YdiPOkbYgy3A48ANwBXAZcCIyQ79gDwAfAe8FeFzQ\n" +
"3gEuBB4B5gATAb6BvlPUpy63wX2JpF0M3I/waT5g+6xkjd0BipGxojdUNjpG5ojNQNjZG\n" +
"64Rx5J7QeMy++3AAAAABJRU5ErkJggg==").bitmap
}
/**
* 推送新 APP 安装适配通知
* @param context 实例
* @param packageName 安装的 APP 包名
*/
fun pushNewAppSupportNotify(context: Context, packageName: String) {
if (context.isAppDebuggable(packageName)) return
context.getSystemService(NotificationManager::class.java)?.apply {
createNotificationChannel(
NotificationChannel(
NOTIFY_CHANNEL, "通知图标优化适配",
NotificationManager.IMPORTANCE_DEFAULT
).apply { enableLights(false) }
)
notify(packageName.hashCode(), Notification.Builder(context, NOTIFY_CHANNEL).apply {
setShowWhen(true)
setContentTitle("您已安装 ${context.findAppName(packageName)}")
setContentText("尚未适配此应用,点按打开在线规则。")
setColor(0xFF2993F0.toInt())
setAutoCancel(true)
setSmallIcon(Icon.createWithBitmap(smallIcon))
setLargeIcon(context.findAppIcon(packageName)?.toBitmap())
setContentIntent(
PendingIntent.getActivity(
context, packageName.hashCode(),
Intent().apply {
component = ComponentName(
Const.MODULE_PACKAGE_NAME,
"${Const.MODULE_PACKAGE_NAME}.ui.activity.ConfigureActivity"
)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}.apply {
putExtra("isNewAppSupport", true)
putExtra("appName", context.findAppName(packageName))
putExtra("pkgName", packageName)
}, if (Build.VERSION.SDK_INT < 31) PendingIntent.FLAG_UPDATE_CURRENT else PendingIntent.FLAG_IMMUTABLE
)
)
}.build())
}
}
/**
* 检测 APP 卸载后移除相关通知
* @param context 实例
* @param packageName 卸载的 APP 包名
*/
fun removeNewAppSupportNotify(context: Context, packageName: String) = runInSafe {
context.getSystemService(NotificationManager::class.java)?.cancel(packageName.hashCode())
}
/**
* 自动更新通知图标优化在线规则
*
* 一天执行一次
* @param context 实例
* @param timeSet 设定的时间
*/
fun prepareAutoUpdateIconRule(context: Context, timeSet: String) = runInSafe {
System.currentTimeMillis().also {
if (it.stampToDate(format = "HH:mm") == timeSet && (outDateLimits.isEmpty() || outDateLimits.none { each ->
each == it.stampToDate(format = "yyyy-MM-dd")
})) {
outDateLimits.add(it.stampToDate(format = "yyyy-MM-dd"))
context.startActivity(
Intent().apply {
component = ComponentName(
Const.MODULE_PACKAGE_NAME,
"${Const.MODULE_PACKAGE_NAME}.ui.activity.auto.NotifyIconRuleUpdateActivity"
)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
)
}
}
}
}

View File

@@ -0,0 +1,457 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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/25.
*/
@file:Suppress("TrustAllX509TrustManager", "CustomX509TrustManager", "DEPRECATION", "IMPLICIT_CAST_TO_ANY")
package com.fankes.coloros.notify.utils.tool
import android.app.Activity
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.provider.Settings
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.NotificationCompat
import androidx.core.content.getSystemService
import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged
import com.fankes.coloros.notify.R
import com.fankes.coloros.notify.data.DataConst
import com.fankes.coloros.notify.databinding.DiaSourceFromBinding
import com.fankes.coloros.notify.databinding.DiaSourceFromStringBinding
import com.fankes.coloros.notify.hook.HookConst.TYPE_SOURCE_SYNC_WAY_1
import com.fankes.coloros.notify.hook.HookConst.TYPE_SOURCE_SYNC_WAY_2
import com.fankes.coloros.notify.hook.HookConst.TYPE_SOURCE_SYNC_WAY_3
import com.fankes.coloros.notify.param.IconPackParams
import com.fankes.coloros.notify.ui.activity.ConfigureActivity
import com.fankes.coloros.notify.utils.factory.safeOfNull
import com.fankes.coloros.notify.utils.factory.showDialog
import com.fankes.coloros.notify.utils.factory.snake
import com.highcapable.yukihookapi.hook.factory.modulePrefs
import com.highcapable.yukihookapi.hook.log.loggerD
import okhttp3.*
import java.io.IOException
import java.security.SecureRandom
import java.security.cert.X509Certificate
import javax.net.ssl.*
/**
* 通知图标在线规则管理类
*/
object IconRuleManagerTool {
/** 推送通知的渠道名称 */
private const val NOTIFY_CHANNEL = "notifyRuleUpdateId"
/** 当前规则的系统名称 */
private const val OS_TAG = "ColorOS"
/** 当前规则的通知图标颜色 */
private const val OS_COLOR = 0xFF4E8A5A
/**
* 从在线地址手动同步规则
* @param context 实例
* @param it 成功后回调
*/
fun syncByHand(context: Context, it: () -> Unit) =
context.showDialog {
title = "同步列表"
var sourceType = context.modulePrefs.get(DataConst.SOURCE_SYNC_WAY)
var customUrl = context.modulePrefs.get(DataConst.SOURCE_SYNC_WAY_CUSTOM_URL)
bind<DiaSourceFromBinding>().apply {
diaSfText.apply {
if (customUrl.isNotBlank()) {
setText(customUrl)
setSelection(customUrl.length)
}
doOnTextChanged { text, _, _, _ -> customUrl = text.toString() }
}
diaSfTextLin.isVisible = sourceType == TYPE_SOURCE_SYNC_WAY_3
diaSfRd1.isChecked = sourceType == TYPE_SOURCE_SYNC_WAY_1
diaSfRd2.isChecked = sourceType == TYPE_SOURCE_SYNC_WAY_2
diaSfRd3.isChecked = sourceType == TYPE_SOURCE_SYNC_WAY_3
diaSfRd1.setOnClickListener {
diaSfRd2.isChecked = false
diaSfRd3.isChecked = false
diaSfTextLin.isVisible = false
sourceType = TYPE_SOURCE_SYNC_WAY_1
}
diaSfRd2.setOnClickListener {
diaSfRd1.isChecked = false
diaSfRd3.isChecked = false
diaSfTextLin.isVisible = false
sourceType = TYPE_SOURCE_SYNC_WAY_2
}
diaSfRd3.setOnClickListener {
diaSfRd1.isChecked = false
diaSfRd2.isChecked = false
diaSfTextLin.isVisible = true
sourceType = TYPE_SOURCE_SYNC_WAY_3
}
}
confirmButton {
context.modulePrefs.put(DataConst.SOURCE_SYNC_WAY, sourceType)
context.modulePrefs.put(DataConst.SOURCE_SYNC_WAY_CUSTOM_URL, customUrl)
sync(context, sourceType, customUrl, it)
}
cancelButton()
neutralButton(text = "自定义规则") {
context.showDialog {
title = "自定义规则(调试)"
val editText = bind<DiaSourceFromStringBinding>().diaSfsInputEdit.apply {
requestFocus()
invalidate()
}
IconPackParams(context).also { params ->
confirmButton(text = "合并") {
editText.text.toString().also { jsonString ->
when {
jsonString.isNotBlank() && params.isNotVaildJson(jsonString) -> context.snake(msg = "不是有效的 JSON 数据")
jsonString.isNotBlank() -> {
params.save(
params.splicingJsonArray(
dataJson1 = params.storageDataJson ?: "[]",
dataJson2 = jsonString.takeIf { params.isJsonArray(it) } ?: "[$jsonString]"
)
)
notifyRefresh(context)
it()
}
else -> context.snake(msg = "请输入有效内容")
}
}
}
cancelButton(text = "覆盖") {
editText.text.toString().also { jsonString ->
when {
jsonString.isNotBlank() && params.isNotVaildJson(jsonString) -> context.snake(msg = "不是有效的 JSON 数据")
jsonString.isNotBlank() -> {
params.save(dataJson = jsonString.takeIf { params.isJsonArray(it) } ?: "[$jsonString]")
notifyRefresh(context)
it()
}
else -> context.snake(msg = "请输入有效内容")
}
}
}
}
neutralButton(text = "取消")
}
}
}
/**
* 从在线地址同步规则
* @param context 实例
* @param sourceType 同步地址类型 - 默认自动获取已存储的键值
* @param customUrl 自定义同步地址 - 默认自动获取已存储的键值
* @param it 成功后回调
*/
fun sync(
context: Context,
sourceType: Int = context.modulePrefs.get(DataConst.SOURCE_SYNC_WAY),
customUrl: String = context.modulePrefs.get(DataConst.SOURCE_SYNC_WAY_CUSTOM_URL),
it: () -> Unit
) {
when (sourceType) {
TYPE_SOURCE_SYNC_WAY_1 ->
onRefreshing(context, url = "https://raw.fastgit.org/fankes/AndroidNotifyIconAdapt/main", it)
TYPE_SOURCE_SYNC_WAY_2 ->
onRefreshing(context, url = "https://raw.githubusercontent.com/fankes/AndroidNotifyIconAdapt/main", it)
TYPE_SOURCE_SYNC_WAY_3 ->
if (customUrl.isNotBlank())
if (customUrl.startsWith("http://") || customUrl.startsWith("https://"))
onRefreshingCustom(context, customUrl, it)
else context.snakeOrNotify(title = "同步失败", msg = "同步地址不是一个合法的 URL")
else context.snakeOrNotify(title = "同步失败", msg = "同步地址不能为空")
else -> context.snakeOrNotify(title = "同步异常", msg = "同步类型错误")
}
}
/**
* 开始更新数据
* @param context 实例
* @param url
* @param it 成功后回调
*/
private fun onRefreshing(context: Context, url: String, it: () -> Unit) = checkingInternetConnect(context) {
fun doParse(callback: (Boolean) -> Unit = {}) {
wait(context, url = "$url/OS/$OS_TAG/NotifyIconsSupportConfig.json") { isDone1, ctOS ->
callback(true)
wait(context, url = "$url/APP/NotifyIconsSupportConfig.json") { isDone2, ctAPP ->
callback(false)
IconPackParams(context).also { params ->
when {
isDone1 && isDone2 -> params.splicingJsonArray(ctOS, ctAPP).also {
when {
params.isHackString(it) ->
context.snakeOrNotify(title = "同步错误", msg = "请求需要验证,请尝试魔法上网或关闭魔法")
params.isNotVaildJson(it) ->
context.snakeOrNotify(title = "同步错误", msg = "目标地址不是有效的 JSON 数据")
params.isCompareDifferent(it) -> {
params.save(it)
pushNotify(context, title = "同步完成", msg = "已更新通知图标优化名单,点击查看")
notifyRefresh(context)
it()
}
else -> (if (context is AppCompatActivity) context.snake(msg = "列表数据已是最新"))
}
}
context is AppCompatActivity ->
context.showDialog {
title = "连接失败"
msg = "连接失败,错误如下:\n${if (isDone1.not()) ctOS else ctAPP}"
confirmButton(text = "再试一次") { syncByHand(context, it) }
cancelButton()
}
else -> pushNotify(context, title = "同步地址不可用", msg = if (isDone1.not()) ctOS else ctAPP)
}
}
}
}
}
if (context is AppCompatActivity)
context.showDialog {
title = "同步中"
progressContent = "正在同步 OS 数据"
noCancelable()
doParse { if (it) progressContent = "正在同步 APP 数据" else cancel() }
}
else doParse()
}
/**
* 开始更新数据
* @param context 实例
* @param url
* @param it 成功后回调
*/
private fun onRefreshingCustom(context: Context, url: String, it: () -> Unit) = checkingInternetConnect(context) {
fun doParse(callback: () -> Unit = {}) {
wait(context, url) { isDone, content ->
callback()
IconPackParams(context).also { params ->
when {
isDone -> when {
params.isHackString(content) ->
context.snakeOrNotify(title = "同步错误", msg = "请求需要验证,请尝试魔法上网或关闭魔法")
params.isNotVaildJson(content) ->
context.snakeOrNotify(title = "同步错误", msg = "目标地址不是有效的 JSON 数据")
params.isCompareDifferent(content) -> {
params.save(content)
pushNotify(context, title = "同步完成", msg = "已更新通知图标优化名单,点击查看")
notifyRefresh(context)
it()
}
else -> (if (context is AppCompatActivity) context.snake(msg = "列表数据已是最新"))
}
context is AppCompatActivity ->
context.showDialog {
title = "连接失败"
msg = "连接失败,错误如下:\n$content"
confirmButton(text = "我知道了")
}
else -> pushNotify(context, title = "同步地址不可用", msg = content)
}
}
}
}
if (context is AppCompatActivity)
context.showDialog {
title = "同步中"
progressContent = "正在通过自定义地址同步数据"
noCancelable()
doParse { cancel() }
}
else doParse()
}
/**
* 检查网络连接情况
* @param context 实例
* @param it 已连接回调
*/
private fun checkingInternetConnect(context: Context, it: () -> Unit) =
if (context is AppCompatActivity) context.showDialog {
title = "准备中"
progressContent = "正在检查网络连接情况"
noCancelable()
baseCheckingInternetConnect(context) { isDone ->
cancel()
if (isDone) it() else
context.showDialog {
title = "网络不可用"
msg = "无法连接到互联网,请检查你当前的设备是否可以上网,且没有在手机管家中禁用本模块的联网权限。"
confirmButton(text = "检查设置") {
runCatching {
context.startActivity(Intent().apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
data = Uri.fromParts("package", context.packageName, null)
})
}.onFailure { context.snake(msg = "启动应用信息页面失败") }
}
cancelButton()
}
}
} else baseCheckingInternetConnect(context) { isDone ->
if (isDone) it() else pushNotify(context, title = "网络不可用", msg = "无法连接到互联网,无法更新通知图标规则")
}
/**
* 检查网络连接情况
* @param context 实例
* @param it 已连接回调
*/
private fun baseCheckingInternetConnect(context: Context, it: (Boolean) -> Unit) =
wait(context, url = "https://www.baidu.com") { isDone, _ -> it(isDone) }
/**
* 发送 GET 请求内容并等待
* @param context 实例
* @param url 请求地址
* @param it 回调 - ([Boolean] 是否成功,[String] 成功的内容或失败消息)
*/
private fun wait(context: Context, url: String, it: (Boolean, String) -> Unit) = runCatching {
OkHttpClient().newBuilder().apply {
SSLSocketClient.sSLSocketFactory?.let { sslSocketFactory(it, SSLSocketClient.trustManager) }
hostnameVerifier(SSLSocketClient.hostnameVerifier)
}.build().newCall(
Request.Builder()
.url(url)
.get()
.build()
).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
(context as? Activity?)?.runOnUiThread { it(false, e.toString()) } ?: it(false, e.toString())
}
override fun onResponse(call: Call, response: Response) {
val bodyString = response.body?.string() ?: ""
(context as? Activity?)?.runOnUiThread { it(true, bodyString) } ?: it(true, bodyString)
}
})
}.onFailure { it(false, "URL 无效") }
/**
* 推送通知图标更新通知
* @param context 实例
*/
private fun notifyRefresh(context: Context) {
if (context !is AppCompatActivity) return
SystemUITool.refreshSystemUI(context) { context.snake(msg = "通知图标优化名单已完成同步") }
}
/**
* 根据实例类型决定推送通知还是弹出提示
* @param title 标题 - 仅对通知生效
* @param msg 消息内容
*/
private fun Context.snakeOrNotify(title: String, msg: String) {
if (this !is AppCompatActivity)
pushNotify(context = this, title, msg)
else snake(msg)
}
/**
* 推送通知
* @param context 实例 - 类型为 [AppCompatActivity] 时将不会推送通知
* @param title 通知标题
* @param msg 通知消息
*/
private fun pushNotify(context: Context, title: String, msg: String) {
if (context !is AppCompatActivity)
context.getSystemService<NotificationManager>()?.apply {
createNotificationChannel(
NotificationChannel(
NOTIFY_CHANNEL, "通知图标优化规则",
NotificationManager.IMPORTANCE_DEFAULT
)
)
notify(0, NotificationCompat.Builder(context, NOTIFY_CHANNEL).apply {
setContentTitle(title)
setContentText(msg)
color = OS_COLOR.toInt()
setAutoCancel(true)
setSmallIcon(R.drawable.ic_nf_icon_update)
setSound(null)
setDefaults(NotificationCompat.DEFAULT_ALL)
setContentIntent(
PendingIntent.getActivity(
context, msg.hashCode(),
Intent(context, ConfigureActivity::class.java).apply { putExtra("isShowUpdDialog", false) },
if (Build.VERSION.SDK_INT < 31) PendingIntent.FLAG_UPDATE_CURRENT else PendingIntent.FLAG_IMMUTABLE
)
)
}.build())
}
}
/**
* 自动信任 SSL 证书
*
* 放行全部加密 SSL 请求
*/
private object SSLSocketClient {
/**
* 格式化实例
* @return [SSLSocketFactory] or null
*/
val sSLSocketFactory
get() = safeOfNull {
SSLContext.getInstance("TLS").let {
it.init(null, arrayOf<TrustManager>(trustManager), SecureRandom())
it.socketFactory
}
}
/**
* 使用的实例
* @return [HostnameVerifier]
*/
val hostnameVerifier get() = HostnameVerifier { _, _ -> true }
/**
* 信任管理者
* @return [X509TrustManager]
*/
val trustManager
get() = object : X509TrustManager {
override fun checkClientTrusted(chain: Array<X509Certificate?>?, authType: String?) {
loggerD(msg = "TrustX509 --> $authType")
}
override fun checkServerTrusted(chain: Array<X509Certificate?>?, authType: String?) {
loggerD(msg = "TrustX509 --> $authType")
}
override fun getAcceptedIssuers() = arrayOf<X509Certificate>()
}
}
}

View File

@@ -0,0 +1,171 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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/8.
*/
package com.fankes.coloros.notify.utils.tool
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import com.fankes.coloros.notify.const.Const
import com.fankes.coloros.notify.utils.factory.*
import com.google.android.material.snackbar.Snackbar
import com.highcapable.yukihookapi.hook.factory.isXposedModuleActive
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication.Companion.appContext
/**
* 系统界面工具
*/
object SystemUITool {
/** 宿主广播回调 */
private var moduleHandlerCallback: ((Boolean, Boolean) -> Unit)? = null
/** 通知广播回调 */
private var remindHandlerCallback: ((Boolean, Boolean) -> Unit)? = null
/**
* 注册广播
* @param context 实例
*/
fun register(context: Context) = runInSafe {
/** 注册广播检查模块激活状态 */
context.registerReceiver(moduleHandlerReceiver, IntentFilter().apply { addAction(Const.ACTION_MODULE_HANDLER_RECEIVER) })
/** 注册广播通知系统界面改变 */
context.registerReceiver(remindHandlerReceiver, IntentFilter().apply { addAction(Const.ACTION_REMIND_HANDLER_RECEIVER) })
}
/**
* 取消注册广播
* @param context 实例
*/
fun unregister(context: Context) = runInSafe {
context.unregisterReceiver(moduleHandlerReceiver)
context.unregisterReceiver(remindHandlerReceiver)
}
/**
* 检查模块是否激活
* @param context 实例
* @param it 成功后回调 - ([Boolean] 是否激活,[Boolean] 是否有效)
*/
fun checkingActivated(context: Context, it: (Boolean, Boolean) -> Unit) {
moduleHandlerCallback = it
context.sendBroadcast(Intent().apply {
action = Const.ACTION_MODULE_CHECKING_RECEIVER
putExtra(Const.MODULE_VERSION_VERIFY_TAG, Const.MODULE_VERSION_VERIFY)
})
}
/**
* 重启系统界面
* @param context 实例
*/
fun restartSystemUI(context: Context) =
context.showDialog {
title = "重启系统界面"
msg = "你确定要立即重启系统界面吗?\n\n" +
"重启过程会黑屏并等待进入锁屏重新解锁。"
confirmButton {
execShell(cmd = "pgrep systemui").also { pid ->
if (pid.isNotBlank())
execShell(cmd = "kill -9 $pid")
else toast(msg = "ROOT 权限获取失败")
}
}
cancelButton()
}
/**
* 刷新系统界面状态栏与通知图标
* @param context 实例
* @param isRefreshCacheOnly 仅刷新缓存不刷新图标和通知改变 - 默认:否
* @param it 成功后回调
*/
fun refreshSystemUI(context: Context? = null, isRefreshCacheOnly: Boolean = false, it: () -> Unit = {}) = runInSafe {
fun sendMessage() {
(context ?: appContext).sendBroadcast(Intent().apply {
action = Const.ACTION_REMIND_CHECKING_RECEIVER
putExtra("isRefreshCacheOnly", isRefreshCacheOnly)
putExtra(Const.MODULE_VERSION_VERIFY_TAG, Const.MODULE_VERSION_VERIFY)
})
}
if (isXposedModuleActive)
context?.showDialog {
title = "请稍后"
progressContent = "正在等待系统界面刷新"
/** 是否等待成功 */
var isWaited = false
/** 设置等待延迟 */
delayedRun(ms = 5000) {
if (isWaited) return@delayedRun
remindHandlerCallback = null
cancel()
context.snake(msg = "预计响应超时,建议重启系统界面", actionText = "立即重启") { restartSystemUI(context) }
}
remindHandlerCallback = { isGrasp, isValied ->
remindHandlerCallback = null
cancel()
isWaited = true
when {
isGrasp && !isValied ->
context.snake(msg = "请重启系统界面以生效模块更新", actionText = "立即重启") { restartSystemUI(context) }
else -> it()
}
}
sendMessage()
noCancelable()
} ?: sendMessage()
else context?.snake(msg = "模块没有激活,更改不会生效")
}
/**
* 显示需要重启系统界面的 [Snackbar]
* @param context 实例
*/
fun showNeedRestartSnake(context: Context) =
if (isXposedModuleActive)
context.snake(msg = "设置需要重启系统界面才能生效", actionText = "立即重启") { restartSystemUI(context) }
else context.snake(msg = "模块没有激活,更改不会生效")
/** 宿主广播接收器 */
private val moduleHandlerReceiver by lazy {
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val isRegular = intent?.getBooleanExtra("isRegular", false) ?: false
val isValied = intent?.getBooleanExtra("isValied", false) ?: false
moduleHandlerCallback?.invoke(isRegular, isValied)
}
}
}
/** 通知广播接收器 */
private val remindHandlerReceiver by lazy {
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val isGrasp = intent?.getBooleanExtra("isGrasp", false) ?: false
val isValied = intent?.getBooleanExtra("isValied", false) ?: false
remindHandlerCallback?.invoke(isGrasp, isValied)
}
}
}
}

View File

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

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@color/white"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM14,13v4h-4v-4H7l5,-5 5,5h-3z" />
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="150dp"
android:height="150dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="@color/white"
android:pathData="M192,736 L192,416C192,261.2 302,132.1 448,102.4L448,64C448,28.6 476.6,0 512,0 547.4,0 576,28.6 576,64L576,102.4C722,132.1 832,261.2 832,416L832,736 864.1,736C899.4,736 928,764.4 928,800 928,835.4 899.4,864 864.1,864L159.9,864C124.6,864 96,835.6 96,800 96,764.6 124.6,736 159.9,736L192,736ZM608,928C608,981 565,1024 512,1024 459,1024 416,981 416,928L608,928Z" />
</vector>

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@color/white"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM15.29,16.71L11,12.41V7h2v4.59l3.71,3.71L15.29,16.71z" />
</vector>

View File

@@ -5,7 +5,7 @@
android:layout_height="match_parent"
android:background="@color/colorThemeBackground"
android:orientation="vertical"
tools:context=".ui.MainActivity"
tools:context=".ui.activity.MainActivity"
tools:ignore="HardcodedText,UseCompoundDrawables,ContentDescription">
<LinearLayout
@@ -24,7 +24,7 @@
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="25dp"
android:layout_marginEnd="20dp"
android:src="@mipmap/ic_back"
android:tint="@color/colorTextGray"
android:tooltipText="返回" />
@@ -32,7 +32,7 @@
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:layout_marginEnd="2.5dp"
android:layout_weight="1"
android:gravity="center|start"
android:orientation="vertical">
@@ -54,7 +54,7 @@
android:singleLine="true"
android:text="适配列表正在等待装载"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
android:textSize="11.5sp" />
</LinearLayout>
<androidx.constraintlayout.utils.widget.ImageFilterView

View File

@@ -6,8 +6,8 @@
android:layout_height="match_parent"
android:background="@color/colorThemeBackground"
android:orientation="vertical"
tools:context=".ui.MainActivity"
tools:ignore="HardcodedText,UseCompoundDrawables,ContentDescription">
tools:context=".ui.activity.MainActivity"
tools:ignore="HardcodedText,UseCompoundDrawables,ContentDescription,TooManyViews">
<LinearLayout
android:layout_width="match_parent"
@@ -34,11 +34,22 @@
style="?android:attr/selectableItemBackgroundBorderless"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginEnd="5dp"
android:layout_marginEnd="15dp"
android:alpha="0.85"
android:src="@mipmap/ic_restart"
android:tint="@color/colorTextGray"
android:tooltipText="重启系统界面" />
<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"
android:tooltipText="项目地址" />
</LinearLayout>
<LinearLayout
@@ -76,28 +87,71 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:ellipsize="end"
android:singleLine="true"
android:text="模块未激活"
android:textColor="@color/white"
android:textSize="18sp" />
<TextView
android:id="@+id/main_text_version"
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:gravity="center|start"
android:orientation="horizontal">
<TextView
android:id="@+id/main_text_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.8"
android:ellipsize="end"
android:singleLine="true"
android:text="模块版本:%1"
android:textColor="@color/white"
android:textSize="13sp" />
<TextView
android:id="@+id/main_text_release_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:background="@drawable/bg_orange_round"
android:ellipsize="end"
android:paddingLeft="5dp"
android:paddingTop="2dp"
android:paddingRight="5dp"
android:paddingBottom="2dp"
android:singleLine="true"
android:text="点击更新 %1"
android:textColor="@color/white"
android:textSize="11sp"
android:visibility="gone" />
</LinearLayout>
<TextView
android:id="@+id/main_text_color_os_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.8"
android:text="模块版本:%1"
android:ellipsize="end"
android:singleLine="true"
android:text="系统版本:%1"
android:textColor="@color/white"
android:textSize="13sp" />
<TextView
android:id="@+id/main_text_coloros_version"
android:id="@+id/main_text_api_way"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.8"
android:text="系统版本:%1"
android:layout_marginTop="5dp"
android:alpha="0.6"
android:ellipsize="end"
android:singleLine="true"
android:text="%1"
android:textColor="@color/white"
android:textSize="13sp" />
android:textSize="11sp"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
@@ -158,9 +212,31 @@
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingTop="15dp"
android:paddingRight="15dp">
<com.fankes.coloros.notify.view.MaterialSwitch
<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_basic" />
<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.coloros.notify.ui.view.MaterialSwitch
android:id="@+id/module_enable_switch"
android:layout_width="match_parent"
android:layout_height="40dp"
@@ -169,7 +245,7 @@
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<com.fankes.coloros.notify.view.MaterialSwitch
<com.fankes.coloros.notify.ui.view.MaterialSwitch
android:id="@+id/module_enable_log_switch"
android:layout_width="match_parent"
android:layout_height="35dp"
@@ -190,7 +266,7 @@
</LinearLayout>
<LinearLayout
android:id="@+id/config_item_dev"
android:id="@+id/dev_notify_config_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
@@ -202,10 +278,32 @@
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingTop="15dp"
android:paddingRight="15dp">
<com.fankes.coloros.notify.view.MaterialSwitch
android:id="@+id/remove_dev_n_enable_switch"
<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_control" />
<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.coloros.notify.ui.view.MaterialSwitch
android:id="@+id/dev_notify_config_switch"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginTop="5dp"
@@ -216,15 +314,15 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginBottom="5dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="每次重新开机后只要开发者选项开启就会推送警告通知,开启后将直接移除这个“你在教我做事”的通知功能。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<com.fankes.coloros.notify.view.MaterialSwitch
android:id="@+id/remove_chargecp_n_enable_switch"
<com.fankes.coloros.notify.ui.view.MaterialSwitch
android:id="@+id/crcp_notify_config_switch"
android:layout_width="match_parent"
android:layout_height="35dp"
android:layout_marginBottom="5dp"
@@ -233,19 +331,37 @@
android:textSize="15sp" />
<TextView
android:id="@+id/color_icon_compat_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginBottom="5dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="充电完成后总是会推送一条通知,有时候甚至还不是静音状态,开启后将直接移除这个“你在教我做事”的通知功能。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<com.fankes.coloros.notify.ui.view.MaterialSwitch
android:id="@+id/dnd_notify_config_switch"
android:layout_width="match_parent"
android:layout_height="35dp"
android:layout_marginBottom="5dp"
android:text="移除免打扰通知"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="开启免打扰后无论任何时长,总是会推送一条通知,开启后将直接移除这个“你在教我做事”的通知功能。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/config_item_a12"
android:id="@+id/a12_style_config_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
@@ -256,14 +372,38 @@
android:elevation="0dp"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingRight="15dp">
android:paddingTop="15dp">
<com.fankes.coloros.notify.view.MaterialSwitch
android:id="@+id/a12_style_enable_switch"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center|start"
android:paddingLeft="15dp"
android:paddingRight="15dp">
<ImageView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="10dp"
android:src="@mipmap/ic_nf_style" />
<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.coloros.notify.ui.view.MaterialSwitch
android:id="@+id/a12_style_config_switch"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginTop="5dp"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:text="启用 Android 12 通知栏风格"
android:textAllCaps="false"
android:textColor="@color/colorTextGray"
@@ -275,13 +415,97 @@
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="无论任何版本,开启后都将使用 Android 12 原生通知图标方式为通知栏推送的通知图标增加外部圆形轮廓。"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:text="无论任何版本,开启后都将使用 Android 12 原生通知图标方式为通知栏推送的通知图标增加外部圆形轮廓。\n目标为 Android 12 的系统将自动使用系统主题色着色。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<com.fankes.coloros.notify.ui.view.MaterialSwitch
android:id="@+id/notify_panel_config_switch"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginTop="5dp"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:text="自定义通知面板背景透明度"
android:textAllCaps="false"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<androidx.appcompat.widget.AppCompatSeekBar
android:id="@+id/notify_panel_config_seekbar"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginLeft="5dp"
android:layout_marginTop="5dp"
android:layout_marginRight="5dp"
android:max="255"
android:min="0" />
<LinearLayout
android:id="@+id/notify_panel_config_text_panel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:paddingTop="5dp"
android:paddingBottom="15dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:alpha="0.75"
android:ellipsize="end"
android:gravity="center"
android:maxWidth="100dp"
android:singleLine="true"
android:text="当前值"
android:textColor="@color/colorTextGray"
android:textSize="13.5sp" />
<TextView
android:id="@+id/notify_panel_config_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="center"
android:maxWidth="100dp"
android:singleLine="true"
android:text="%1"
android:textColor="@color/colorTextGray"
android:textSize="15sp"
android:textStyle="bold" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:text="开启自定义功能后你可以拖拽滑动条来调整通知面板的透明度0 为全透明255 为不透明。"
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:paddingLeft="15dp"
android:paddingRight="15dp"
android:text="此功能为实验性功能,后期不一定继续维护,且仅在 ColorOS 12.1 测试通过,不能保证可以在任何系统版本中生效,以及和各种通知栏主题模块配合使用,如有冲突请关闭此功能或禁用相关重复功能的主题模块。\n已知问题悬浮通知也会被透明在透明度较低的时候下拉会有阴影光晕后期看需求再解决。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/config_item_notify"
android:id="@+id/notify_icon_config_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
@@ -292,10 +516,34 @@
android:elevation="0dp"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingRight="15dp">
android:paddingLeft="10dp"
android:paddingTop="15dp"
android:paddingRight="10dp">
<com.fankes.coloros.notify.view.MaterialSwitch
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:gravity="center|start">
<ImageView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="10dp"
android:src="@mipmap/ic_notify" />
<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.coloros.notify.ui.view.MaterialSwitch
android:id="@+id/notify_icon_fix_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -306,9 +554,11 @@
android:textSize="15sp" />
<TextView
android:id="@+id/config_notify_app_button"
android:id="@+id/notify_icon_fix_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:layout_marginBottom="10dp"
android:background="@drawable/bg_button_round"
android:gravity="center"
@@ -326,7 +576,7 @@
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="首次安装请打开名单列表从云端更新数据,后期适配的内容也请手动打开名单列表重新拉取数据以检查更新,数据更新后重启系统界面即可生效。"
android:text="首次安装请打开名单列表从云端更新数据,后期适配的内容也请手动打开名单列表重新拉取数据以检查更新,数据更新后无需重启系统界面,实时生效。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
@@ -341,6 +591,119 @@
android:text="此选项默认开启,开启后将对优化名单内的 APP 通知小图标使用单色调进行修复,特别是通过系统级别推送的通知,它们始终是 APP 默认图标(彩色的 APP 图标),修复后使得它们的图标看起来更加符合 Android 原生的统一规范。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<LinearLayout
android:id="@+id/notify_icon_fix_notify_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:orientation="vertical">
<com.fankes.coloros.notify.ui.view.MaterialSwitch
android:id="@+id/notify_icon_fix_notify_switch"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_marginBottom="5dp"
android:text="提醒未适配通知图标的新安装应用"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="此选项默认开启,在通知图标优化名单有数据时将自动检查新安装的应用是否存在适配数据,若没有将会发送通知提醒,如果你了解日后新安装的应用通知图标是遵守原生单色图标规范的,可以关闭此提醒。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/notify_icon_auto_sync_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:animateLayoutChanges="true"
android:orientation="vertical">
<com.fankes.coloros.notify.ui.view.MaterialSwitch
android:id="@+id/notify_icon_auto_sync_switch"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_marginBottom="5dp"
android:text="启用通知图标优化名单自动更新"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="此选项默认开启,为确保名单内的数据为云端最新版本,你可以设置每天自动更新在线规则的时间,自动更新的地址为你最后一次成功设置的在线规则同步地址。\n请确保模块能够后台联网并不被阻止其它 APP 唤醒,否则自动同步可能会失败。\n模块无需保持在后台运行到达同步时间后会自动启动如果到达时间后模块正在运行则会自动取消本次计划任务。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<LinearLayout
android:id="@+id/notify_icon_auto_sync_child_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:gravity="center">
<ImageView
android:layout_width="17dp"
android:layout_height="17dp"
android:src="@drawable/ic_system_clock"
app:tint="@color/colorTextGray" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:ellipsize="end"
android:gravity="center"
android:singleLine="true"
android:text="每天"
android:textColor="@color/colorTextGray"
android:textSize="14sp" />
<TextView
android:id="@+id/notify_icon_auto_sync_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:ellipsize="end"
android:gravity="center"
android:maxWidth="100dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:singleLine="true"
android:text="07:00"
android:textColor="@color/colorTextGray"
android:textSize="14sp" />
<TextView
android:id="@+id/notify_icon_auto_sync_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_button_round"
android:ellipsize="end"
android:gravity="center"
android:paddingLeft="10dp"
android:paddingTop="5dp"
android:paddingRight="10dp"
android:paddingBottom="5dp"
android:singleLine="true"
android:text="修改时间"
android:textColor="@color/colorTextGray"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<LinearLayout
@@ -354,9 +717,31 @@
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingTop="15dp"
android:paddingRight="15dp">
<com.fankes.coloros.notify.view.MaterialSwitch
<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.coloros.notify.ui.view.MaterialSwitch
android:id="@+id/hide_icon_in_launcher_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -395,28 +780,27 @@
android:elevation="0dp"
android:gravity="center"
android:orientation="vertical"
android:padding="15dp">
android:padding="15dp"
android:paddingTop="15dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginBottom="15dp"
android:gravity="center|start">
<androidx.constraintlayout.utils.widget.ImageFilterView
<ImageView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="5dp"
android:alpha="0.85"
android:src="@mipmap/ic_about"
android:tint="@color/colorTextGray" />
android:layout_marginEnd="10dp"
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:text="使用帮助&amp;说明"
android:textColor="@color/colorTextGray"
android:textSize="12sp" />
</LinearLayout>
@@ -426,7 +810,7 @@
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:lineSpacingExtra="10dp"
android:text="Q.这个模块是如何诞生的?\nA.继 MIUI 之后的第二大系统 ColorOS 虽然支持原生通知图标,但是第三方推送五颜六色的图标系统并没有做适配,甚至系统自己的图标都是彩色的,极其不友好,而且从 ColorOS 12 开始,原生图标丢失了着色属性,这也是一种对原生 Android 生态的破坏。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
@@ -436,7 +820,7 @@
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:lineSpacingExtra="10dp"
android:text="Q.如何使用?\nA.模块仅支持 LSPosedEdXposed 也可以使用但随时停止支持,由于模块涉及到修改系统应用,不支持其它 Hook 框架,在 LSPosed 的作用域中,只需勾选“系统界面”即可,应用设置后需要重启系统界面。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
@@ -446,8 +830,8 @@
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:text="Q.哪些是已知问题?\nA.问题如下:\n1.由于机型有限,仅对 ColorOS 12~12.1 测试正常运行,根据酷友的需要云调试修复 ColorOS 11 的问题,其它版本情况未知,请自行进行测试。\n2.OxygenOS 只支持 12 版本,其它类 ColorOS 魔改的 UI 理论没有问题,请自行测试。"
android:lineSpacingExtra="10dp"
android:text="Q.哪些是已知问题?\nA.以下是问题描述列表:\n(1) 由于机型有限,仅对 ColorOS 12~12.1 测试正常运行,根据酷友的需要云调试修复 ColorOS 11 的问题,其它版本情况未知,请自行进行测试。\n(2) OxygenOS 只支持 12 版本,其它类 ColorOS 魔改的 UI 理论没有问题,请自行测试。\n(3) 动态刷新系统界面为实验性功能,若动态地改变了系统深色模式,调节了分辨率和字体大小,状态栏的通知图标会还原,或在刷新时发生图标黑白块问题,发生类似这种情况请手动重启一次系统界面即可解决。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
@@ -455,59 +839,12 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:lineSpacingExtra="10dp"
android:text="Q.如何反馈问题?\nA.酷安关注 @星夜不荟"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
</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/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">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="5dp"
android:alpha="0.85"
android:src="@mipmap/ic_about"
android:tint="@color/colorTextGray" />
<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>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:text="本软件是免费开源项目,遵循 AGPL3.0 协议,你可以点击这里前往 Github 查看源码以及获取模块更新。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -558,19 +895,34 @@
android:textSize="16sp" />
</LinearLayout>
<TextView
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_marginBottom="10dp"
android:autoLink="web"
android:background="@drawable/bg_permotion_round"
android:lineSpacingExtra="6dp"
android:padding="10dp"
android:text="此模块使用 YukiHookAPI 构建。\n点击这里了解更多 https://github.com/fankes/YukiHookAPI"
android:textColor="@color/colorTextGray"
android:textSize="12sp" />
android:gravity="center|start"
android:orientation="horizontal"
android:padding="10dp">
<ImageView
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginEnd="10dp"
android:src="@mipmap/ic_yukihookapi" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autoLink="web"
android:ellipsize="end"
android:lineSpacingExtra="6dp"
android:maxLines="2"
android:text="此模块使用 YukiHookAPI 构建。\n了解更多 https://github.com/fankes/YukiHookAPI"
android:textColor="@color/colorTextGray"
android:textSize="11sp" />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>

View File

@@ -68,7 +68,7 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<com.fankes.coloros.notify.view.MaterialSwitch
<com.fankes.coloros.notify.ui.view.MaterialSwitch
android:id="@+id/adp_app_open_switch"
android:layout_width="wrap_content"
android:layout_height="30dp"
@@ -77,7 +77,7 @@
android:textColor="@color/colorTextGray"
android:textSize="13sp" />
<com.fankes.coloros.notify.view.MaterialSwitch
<com.fankes.coloros.notify.ui.view.MaterialSwitch
android:id="@+id/adp_app_all_switch"
android:layout_width="wrap_content"
android:layout_height="30dp"

View File

@@ -9,6 +9,17 @@
android:paddingRight="15dp"
tools:ignore="HardcodedText">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_marginBottom="17.5dp"
android:lineSpacingExtra="6dp"
android:text="此功能仅用于调试单条规则或多条规则,同步最新在线规则后这里的内容将会被覆盖清空。"
android:textColor="@color/colorTextDark"
android:textSize="13sp" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 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.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -1,4 +1,4 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<resources>
<!-- Base application theme. -->
<style name="Theme.ColorOSNotifyIcon" parent="Theme.Material3.DayNight">
<!-- Primary brand color. -->
@@ -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>
<string-array name="module_scope">
<item>com.android.systemui</item>
</string-array>
</resources>

View File

@@ -1,3 +1,4 @@
<resources>
<string name="app_name">ColorOS 通知图标增强</string>
<string name="tile_name">通知图标</string>
</resources>

View File

@@ -1,4 +1,4 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<resources>
<!-- Base application theme. -->
<style name="Theme.ColorOSNotifyIcon" parent="Theme.Material3.DayNight">
<!-- Primary brand color. -->
@@ -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>

View File

@@ -1,12 +1,13 @@
plugins {
id 'com.android.application' version '7.1.2' apply false
id 'com.android.library' version '7.1.2' apply false
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
id 'com.android.application' version '7.1.3' apply false
id 'com.android.library' version '7.1.3' apply false
id 'org.jetbrains.kotlin.android' version '1.6.21' apply false
}
ext {
appVersionName = "1.35"
appVersionCode = 5
appVersionName = "1.76"
appVersionCode = 14
enableR8 = true
}
task clean(type: Delete) {