67 Commits
2.3 ... 2.8

Author SHA1 Message Date
eb75c1c511 Update version to 2.8 2022-06-26 23:53:19 +08:00
efc192b0ee Fix MIUI 12、12.5 (Android 10) not work bug 2022-06-26 23:49:48 +08:00
7b083daa3d Update Gradle & Kotlin & PlatformSDK
- Update Kotlin version to 1.7.0
- Update Gradle dependencies
- Merge legacy code
2022-06-10 17:21:33 +08:00
6b18128a57 Merge code 2022-06-08 19:12:10 +08:00
553098f6bf Make UI to Primary Theme 2022-06-08 15:11:33 +08:00
9499727087 Merge DialogBuilderFactory with new code style 2022-06-07 16:55:51 +08:00
0036ba0090 Update version to 2.75 2022-06-04 02:49:14 +08:00
2e62939181 Fix GithubReleaseTool to LocalTime 2022-06-04 02:47:18 +08:00
30acbff3d5 更新文案 2022-06-04 02:24:47 +08:00
093529baea 修复金凡的 MIUI 通知样式的的通知图标在部分最新的开发版中出现黑白块的问题 2022-06-04 02:16:50 +08:00
d9d40ac08e Added BuildConfig.VERSION_NAME changed 2022-06-03 23:53:37 +08:00
99bd924c66 Merge ListView's adapter to BaseAdapterFactory 2022-06-03 02:01:58 +08:00
d14b2cb551 Update version to 2.7 2022-05-31 03:54:21 +08:00
a864d4aa32 Added notify icon custom corner function 2022-05-31 03:49:10 +08:00
12029cf550 Update misc 2022-05-31 03:49:03 +08:00
ac4544e992 Added lower Android version warn 2022-05-31 02:27:35 +08:00
7f0597f034 Update YukiHookAPI 2022-05-31 02:10:49 +08:00
a7c02467de Fix mi push icon wrong when open force app icon in notify panel 2022-05-30 23:32:40 +08:00
107569607d Update version to 2.6 2022-05-30 03:47:52 +08:00
5d099d2ec6 Changed contributing way 2022-05-30 03:46:20 +08:00
45da218cc8 Added notify icon in notify panel used app icon function 2022-05-30 03:22:41 +08:00
9a8cc9378c Added warn dialog when enable replace all notify icon 2022-05-30 02:34:18 +08:00
d7620f62b1 Merge code 2022-05-30 02:13:49 +08:00
cf9f81be23 Merge to new way support receiver callback 2022-05-30 01:57:55 +08:00
2e1929f36e Merge systemBar support with native 2022-05-30 00:50:11 +08:00
cb6cbf0398 Added YukiPromoteTool 2022-05-30 00:35:03 +08:00
dbdd0fc514 Update YukiHookAPI 2022-05-29 04:17:27 +08:00
21f4ff9ad3 Update YukiHookAPI 2022-05-27 03:35:14 +08:00
1bc79056a9 Update YukiHookAPI 2022-05-25 04:38:00 +08:00
41cb741dff Changed FunctionFactory.kt 2022-05-12 01:09:16 +08:00
6578027a34 Changed Xposed Scope 2022-05-11 21:52:13 +08:00
01cb647f0c Update YukiHookAPI 2022-05-10 01:52:39 +08:00
171cd6a6d1 Make to pending 2022-05-09 01:01:33 +08:00
02f8852ab0 Fix Activity destroy non-null unregister bug 2022-05-09 00:56:26 +08:00
826805c552 Make SystemUIHooker singleton 2022-05-08 15:04:21 +08:00
627697d586 Update YukiHookAPI 2022-05-06 15:08:21 +08:00
f5615ff89f Update YukiHookAPI 2022-05-04 14:02:55 +08:00
dd45e57b00 Update YukiHookAPI 2022-05-04 10:15:02 +08:00
d73239de73 Update YukiHookAPI 2022-05-04 09:31:54 +08:00
deb0ca794f Update Kotlin version 2022-05-04 07:01:44 +08:00
84b3648626 Update YukiHookAPI 2022-05-04 06:55:49 +08:00
ae8fb6514c Update README.md 2022-05-04 06:45:14 +08:00
197f427adb Update YukiHookAPI 2022-05-01 11:59:30 +08:00
58a23b4537 Update README.md/PRIVACY.md 2022-04-26 00:28:05 +08:00
72bbdb69a8 Update PRIVACY.md 2022-04-26 00:20:42 +08:00
679ae26462 Merge code 2022-04-25 02:46:53 +08:00
eefa7ee9e7 Update YukiHookAPI 2022-04-18 03:13:50 +08:00
217d5d9bc3 Merge code 2022-04-15 15:28:59 +08:00
4d1e383ef5 Update YukiHookAPI 2022-04-15 05:18:34 +08:00
25399cd88b Update version to 2.5 2022-04-14 03:11:17 +08:00
18691d8b8d 更换通知刷新方案,彻底解决系统界面卡死问题 2022-04-14 02:55:43 +08:00
7c61b09054 Update YukiHookAPI 2022-04-13 04:59:35 +08:00
7a7e179a2b Merge code 2022-04-11 23:07:37 +08:00
dbd7dbf172 Update YukiHookAPI 2022-04-10 03:13:04 +08:00
502e5dc04b Update YukiHookAPI 2022-04-09 02:32:13 +08:00
ff0c57319a Update README.md 2022-04-09 01:50:24 +08:00
647a39c327 Update README.md 2022-04-05 22:45:32 +08:00
04e8d43356 加入新安装应用的通知图标优化适配通知忽略 DEBUG 版本的 APP 2022-04-05 21:56:37 +08:00
f85d2db36e Update YukiHookAPI 2022-04-04 22:55:09 +08:00
2e5284ebe9 Update version to 2.35 2022-04-04 13:55:10 +08:00
abffc4d853 Update version to 2.35 2022-04-04 13:54:42 +08:00
5d81b8ff87 尝试修复 MIUI 12 的问题 2022-04-04 13:48:54 +08:00
d91d2736e6 修复图标缓存过大造成系统界面停止运行的问题 2022-04-04 13:38:38 +08:00
72169989f2 Merge code 2022-04-04 12:04:12 +08:00
c85da002f5 Update YukiHookAPI 2022-04-04 03:13:19 +08:00
c09a1b5760 Update README.md 2022-03-30 20:56:52 +08:00
c30e56c5aa Update YukiHookAPI 2022-03-30 14:13:48 +08:00
34 changed files with 1397 additions and 837 deletions

2
.idea/misc.xml generated
View File

@@ -23,7 +23,7 @@
<entry key="app/src/main/res/layout-w936dp/dia_status_icon_cout.xml" value="0.935546875" /> <entry key="app/src/main/res/layout-w936dp/dia_status_icon_cout.xml" value="0.935546875" />
<entry key="app/src/main/res/layout/activity_config.xml" value="0.42168674698795183" /> <entry key="app/src/main/res/layout/activity_config.xml" value="0.42168674698795183" />
<entry key="app/src/main/res/layout/activity_login.xml" value="0.4375" /> <entry key="app/src/main/res/layout/activity_login.xml" value="0.4375" />
<entry key="app/src/main/res/layout/activity_main.xml" value="0.32507739938080493" /> <entry key="app/src/main/res/layout/activity_main.xml" value="0.30387540746106484" />
<entry key="app/src/main/res/layout/adapter_config.xml" value="0.375" /> <entry key="app/src/main/res/layout/adapter_config.xml" value="0.375" />
<entry key="app/src/main/res/layout/dia_icon_filter.xml" value="0.4307692307692308" /> <entry key="app/src/main/res/layout/dia_icon_filter.xml" value="0.4307692307692308" />
<entry key="app/src/main/res/layout/dia_icon_search.xml" value="0.4307692307692308" /> <entry key="app/src/main/res/layout/dia_icon_search.xml" value="0.4307692307692308" />

359
PRIVACY.md Normal file
View File

@@ -0,0 +1,359 @@
### MIUI 原生通知图标 隐私政策
> 简体中文
版本1.0
修订与生效日期2022年4月25日
#### 前言
为帮助您使用 MIUI 原生通知图标产品(以下简称“本产品”)或服务,我们可能收集与提供服务相关的设备信息或申请设备权限,您有权拒绝或撤回授权;
我们采取了互联网业内标准的技术措施和数据安全措施来保护您的设备信息安全;
除非再次征得您的同意,我们不会将您的设备信息用于本政策未载明的其他目的;
在阅读本协议前,请确认您已年满 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 地址通常可用于识别设备连接至互联网时所在的位置。
信息收集技术: 我们在《MIUI 原生通知图标 隐私政策》中所述的“自动收集”包括以下方式: 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。
### MIUINativeNotifyIcon Privacy Policy
> English
Version: 1.0
Amendment and effective date: April 25, 2022
#### Preface
In order to help you use MIUINativeNotifyIcon 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 "MIUINativeNotifyIcon 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

@@ -2,52 +2,61 @@
[![Blank](https://img.shields.io/badge/build-passing-brightgreen)](https://github.com/fankes/MIUINativeNotifyIcon) [![Blank](https://img.shields.io/badge/build-passing-brightgreen)](https://github.com/fankes/MIUINativeNotifyIcon)
[![Blank](https://img.shields.io/badge/license-AGPL3.0-blue)](https://github.com/fankes/MIUINativeNotifyIcon/blob/master/LICENSE) [![Blank](https://img.shields.io/badge/license-AGPL3.0-blue)](https://github.com/fankes/MIUINativeNotifyIcon/blob/master/LICENSE)
[![Blank](https://img.shields.io/badge/version-v2.3-green)](https://github.com/fankes/MIUINativeNotifyIcon/releases) [![Blank](https://img.shields.io/badge/version-v2.8-green)](https://github.com/fankes/MIUINativeNotifyIcon/releases)
[![Blank](https://img.shields.io/github/downloads/fankes/MIUINativeNotifyIcon/total?label=Release)](https://github.com/fankes/MIUINativeNotifyIcon/releases) [![Blank](https://img.shields.io/github/downloads/fankes/MIUINativeNotifyIcon/total?label=Release)](https://github.com/fankes/MIUINativeNotifyIcon/releases)
[![Blank](https://img.shields.io/github/downloads/Xposed-Modules-Repo/com.fankes.miui.notify/total?label=LSPosed%20Repo&logo=Android&style=flat&labelColor=F48FB1&logoColor=ffffff)](https://github.com/Xposed-Modules-Repo/com.fankes.miui.notify/releases) [![Blank](https://img.shields.io/github/downloads/Xposed-Modules-Repo/com.fankes.miui.notify/total?label=LSPosed%20Repo&logo=Android&style=flat&labelColor=F48FB1&logoColor=ffffff)](https://github.com/Xposed-Modules-Repo/com.fankes.miui.notify/releases)
[![Telegram](https://img.shields.io/static/v1?label=Telegram&message=交流讨论&color=0088cc)](https://t.me/XiaofangInternet) [![Telegram](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram)](https://t.me/XiaofangInternet)
<br/><br/> <br/><br/>
<img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/app/src/main/ic_launcher-playstore.png" width = "100" height = "100"/> <img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/app/src/main/ic_launcher-playstore.png" width = "100" height = "100"/>
<br/> <br/>
Fix the native notification bar icon function abandoned by the MIUI development team.<br/> Fix the native notification bar icon function abandoned by the MIUI development team.<br/>
修复被 MIUI 开发组丢弃的原生通知图标,支持 MIUI 12、12.5、13 以及最新版本。 修复被 MIUI 开发组丢弃的原生通知图标,支持 MIUI 12、12.5、13 以及最新版本。
# Developer ## Developer
[酷安 @星夜不荟](http://www.coolapk.com/u/876977) [酷安 @星夜不荟](http://www.coolapk.com/u/876977)
# 适配说明 ## 适配说明
- 此模块仅支持 <b>LSPosed</b>(作用域“系统界面”)、<b>~~EdXposed(随时停止支持)~~</b>、不支持<b>太极、无极</b> - 此模块仅支持 <b>LSPosed</b>(作用域“系统界面”)、<b>~~EdXposed(随时停止支持)~~</b>、不支持<b>太极、无极</b>
- 请确保你使用的是 MIUI 官方版本,任何第三方官改包发生的问题,开发者没有义务去解决和修复,请自求多福 - 请确保你使用的是 MIUI 官方版本,任何第三方官改包发生的问题,开发者没有义务去解决和修复,请自求多福
- 目前最低支持基于 Android 9 版本的 MIUI 12 或 MIUI 12.5(最低建议) - 目前最低支持基于 Android 9 版本的 MIUI 12 或 MIUI 12.5(最低建议)
- 建议最低从 MIUI 12.5 `2021-5-18` 开发版以后开始使用模块,之前的版本可能或多或少存在 MIUI 自身 BUG 不生效、黑白块的问题 - 建议最低从 MIUI 12.5 `2021-5-18` 开发版以后开始使用模块,之前的版本可能或多或少存在 MIUI 自身 BUG 不生效、黑白块的问题
- 请始终保持最新版本的 LSPosed旧版本可能会出现 Hook 不生效的问题,若最新版本依然不生效请在作用域中长按“系统界面”(“系统 UI”)选择重新优化 - 请始终保持最新版本的 LSPosed旧版本可能会出现 Hook 不生效的问题,若最新版本依然不生效请在作用域中长按“系统界面”(“系统 UI”)选择重新优化
# 请勿用于非法用途 ## 请勿用于非法用途
- 本模块完全开源免费,如果好用你可以打赏支持开发,但是请不要用于非法用途。 - 本模块完全开源免费,如果好用你可以打赏支持开发,但是请不要用于非法用途。
- 本模块发布地址仅有 [Xposed-Modules-Repo](https://github.com/Xposed-Modules-Repo/com.fankes.miui.notify/releases)、 - 本模块发布地址仅有 [Xposed-Modules-Repo](https://github.com/Xposed-Modules-Repo/com.fankes.miui.notify/releases)、
[Release](https://github.com/fankes/MIUINativeNotifyIcon/releases) [Release](https://github.com/fankes/MIUINativeNotifyIcon/releases)、[蓝奏云](https://fankes.lanzouy.com/b030o2e8h) 及**酷安应用市场**
及 [蓝奏云](https://fankes.lanzouy.com/b030o2e8h),从其他非正规渠道下载到的版本或对您造成任何影响均与我们无关。 ,从其他非正规渠道下载到的版本或对您造成任何影响均与我们无关。
# 贡献通知图标优化名单 ## 贡献通知图标优化名单
此项目是 `AndroidNotifyIconAdapt` 项目的一部分,详情请参考下方。<br/> 此项目是 `AndroidNotifyIconAdapt` 项目的一部分,详情请参考下方。
- [Android 通知图标规范适配计划](https://github.com/fankes/AndroidNotifyIconAdapt) - [Android 通知图标规范适配计划](https://github.com/fankes/AndroidNotifyIconAdapt)
# 历史背景 ## 历史背景
这个模块诞生来源于 MIUI 的乱改和不规范,本来 MIUI 9 之后,官方给出了原生通知图标样式,后面由于用户反应通知栏经常出现黑白块。
这当然不是系统的错,而是国内 APP 和 `MIPUSH` 的通知极其不规范的通知图标设计。
但是呢,接到反馈后 MIUI 开发组选择直接忽略这个问题,在 `2021-5-18` 的开发版开始,把全部通知图标都改成了 APP 的彩色图标,使得之前拥有自有样式的原生图标也被破坏。
对于 Android 开发者来说,官方文档中的 `setSmallIcon` 不再适用于魔改后的 MIUI这将会严重破坏非常多的状态图标。
当然,国内的手机生态除了 `MIPUSH` 的营销通知就是社交软件的通知,可能大部分人都不会在意这件事情。
这个模块诞生来源于 MIUI 的乱改和不规范,本来 MIUI 9 之后,官方给出了原生通知图标样式,后面由于用户反应通知栏经常出现黑白块。<br/><br/>
这当然不是系统的错,而是国内 APP 和 `MIPUSH` 的通知极其不规范的通知图标设计。<br/><br/>
但是呢,接到反馈后 MIUI 开发组选择直接忽略这个问题,在 `2021-5-18` 的开发版开始,把全部通知图标都改成了 APP 的彩色图标,使得之前拥有自有样式的原生图标也被破坏。<br/><br/>
对于 Android 开发者来说,官方文档中的 `setSmallIcon` 不再适用于魔改后的 MIUI这将会严重破坏非常多的状态图标。<br/><br/>
当然,国内的手机生态除了 `MIPUSH` 的营销通知就是社交软件的通知,可能大部分人都不会在意这件事情。<br/><br/>
但是,这个模块就是为了修复被 MIUI 开发组忽略的图标问题才诞生的,并完美地给 MIUI 修复了黑白块图标的问题。 但是,这个模块就是为了修复被 MIUI 开发组忽略的图标问题才诞生的,并完美地给 MIUI 修复了黑白块图标的问题。
<br/>
# 探索历程 ## 探索历程
原生 Android 的小图标和通知图标具有状态性。<br/><br/> 原生 Android 的小图标和通知图标具有状态性。<br/><br/>
<img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/images/native.jpg" height = "35"/><br/><br/> <img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/images/native.jpg" height = "35"/><br/><br/>
@@ -59,9 +68,11 @@ Fix the native notification bar icon function abandoned by the MIUI development
而 MIUI 做了什么呢?<br/><br/> 而 MIUI 做了什么呢?<br/><br/>
<img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/images/miui_n_1.jpg" height = "100"/><br/> <img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/images/miui_n_1.jpg" height = "100"/><br/>
<img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/images/miui_n_2.jpg" height = "100"/><br/><br/> <img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/images/miui_n_2.jpg" height = "100"/><br/><br/>
不曾记得是什么版本开始MIUI 把通知图标改成了这个鬼样子,寻找开发组提案也是无人问津,最后转念一想,自己干吧。<br/><br/> 不曾记得是什么版本开始MIUI 把通知图标改成了这个鬼样子,寻找开发组提案也是无人问津,最后转念一想,自己干吧。
由于目前大量通知图标都来自 `MIPUSH` 发出的营销通知,而 `MIPUSH` 的图标都是统一的彩色应用图标,很多应用也没有适配这一特性, 在通知栏广告满天飞的情况下MIUI 由于目前大量通知图标都来自 `MIPUSH` 发出的营销通知,而 `MIPUSH` 的图标都是统一的彩色应用图标,很多应用也没有适配这一特性, 在通知栏广告满天飞的情况下MIUI
选择放弃原生通知功能,而做出这种违反原生通知规则的做法,而这些彩色图标被设置为单色调图标,也确实会发生黑白块的问题,但是同时又会破坏遵守规范的图标。<br/><br/> 选择放弃原生通知功能,而做出这种违反原生通知规则的做法,而这些彩色图标被设置为单色调图标,也确实会发生黑白块的问题,但是同时又会破坏遵守规范的图标。
真的没有办法了吗?在不断探索下,我找到了原生支持色彩判断的类。 真的没有办法了吗?在不断探索下,我找到了原生支持色彩判断的类。
```kotlin ```kotlin
@@ -74,18 +85,32 @@ com.android.internal.util.ContrastColorUtil
ContrastColorUtil.getInstance().isGrayscaleIcon(drawable) ContrastColorUtil.getInstance().isGrayscaleIcon(drawable)
``` ```
问题就被解决了,顺便修了一下被 MIUI 破坏的通知图标以及优化了一下应用本身方块图标的圆角......<br/><br/> 问题就被解决了,顺便修了一下被 MIUI 破坏的通知图标以及优化了一下应用本身方块图标的圆角......
最后,我想大声问一句 MIUI 开发组:“就这?” 就这么简单的问题为什么拖了这么长时间也没有结论,还要交给用户去修复,这真的是一种负责任的表现吗?<br/><br/>
后来一想,也是啊,被国内生态毒害的用户,怎么可能会去想到这些问题呢,最后只能是我自作多情,还对 MIUI 留有一点情怀吧。<br/><br/> 最后,我想大声问一句 MIUI 开发组:“就这?” 就这么简单的问题为什么拖了这么长时间也没有结论,还要交给用户去修复,这真的是一种负责任的表现吗?
后来一想,也是啊,被国内生态毒害的用户,怎么可能会去想到这些问题呢,最后只能是我自作多情,还对 MIUI 留有一点情怀吧。
——来自一个无可奈何的 MIUI 老用户 ——来自一个无可奈何的 MIUI 老用户
# 后记 ## 后记
近期重新适配了 MIUI 12、12.5、13 版本,每个版本的图标设置方法都不一样,而且改的乱七八糟的,我都要无语了,只能用了很多折中方案,毕竟我也没有那么大精力每个版本去修复,实在是累了。
特地的把自己能有的小米手机刷成各种 MIUI 版本去为酷友做专项适配,我也是很累了,也希望你们能够多多支持,也能让 MIUI 做得更好。
近期重新适配了 MIUI 12、12.5、13 版本,每个版本的图标设置方法都不一样,而且改的乱七八糟的,我都要无语了,只能用了很多折中方案,毕竟我也没有那么大精力每个版本去修复,实在是累了。<br/><br/>
特地的把自己能有的小米手机刷成各种 MIUI 版本去为酷友做专项适配,我也是很累了,也希望你们能够多多支持,也能让 MIUI 做得更好。<br/><br/>
MIUI 再不重写,怕是永远会变成安卓之光。雷军,金凡!! MIUI 再不重写,怕是永远会变成安卓之光。雷军,金凡!!
# 许可证 ## 捐赠支持
- 工作不易,无意外情况此项目将继续维护下去,提供更多可能,欢迎打赏。<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/MIUINativeNotifyIcon/blob/master/PRIVACY.md)
## 许可证
- [AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.html) - [AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.html)
@@ -106,5 +131,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/>. 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) 版权所有 © 2019-2022 Fankes Studio(qzmmcn@163.com)

View File

@@ -1,11 +1,11 @@
plugins { plugins {
id 'com.android.application' id 'com.android.application'
id 'kotlin-android' id 'kotlin-android'
id 'com.google.devtools.ksp' version '1.6.10-1.0.4' id 'com.google.devtools.ksp' version '1.7.0-1.0.6'
} }
android { android {
compileSdk 31 compileSdk 32
signingConfigs { signingConfigs {
debug { debug {
@@ -21,24 +21,18 @@ android {
defaultConfig { defaultConfig {
applicationId "com.fankes.miui.notify" applicationId "com.fankes.miui.notify"
minSdk 28 minSdk 28
targetSdk 31 targetSdk 32
versionCode rootProject.ext.appVersionCode versionCode rootProject.ext.appVersionCode
versionName rootProject.ext.appVersionName versionName rootProject.ext.appVersionName
kotlinOptions {
freeCompilerArgs = [
'-Xno-param-assertions',
'-Xno-call-assertions',
'-Xno-receiver-assertions'
]
}
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
release { release {
minifyEnabled true minifyEnabled rootProject.ext.enableR8
shrinkResources rootProject.ext.enableR8
zipAlignEnabled rootProject.ext.enableR8
signingConfig signingConfigs.debug signingConfig signingConfigs.debug
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
@@ -49,40 +43,31 @@ android {
} }
kotlinOptions { kotlinOptions {
jvmTarget = '11' jvmTarget = '11'
freeCompilerArgs = [
'-Xno-param-assertions',
'-Xno-call-assertions',
'-Xno-receiver-assertions'
]
} }
buildFeatures { buildFeatures {
viewBinding true viewBinding true
} }
} lintOptions {
checkReleaseBuilds false
/** 移除无效耗时 lint Task */ }
tasks.whenTaskAdded {
task -> if (task.name == "lintVitalRelease") task.enabled = false
}
/** 移除无效耗时 lint Task */
tasks.whenTaskAdded {
task -> if (task.name == "lintVitalAnalyzeRelease") task.enabled = false
}
/** 移除无效耗时 lint Task */
tasks.whenTaskAdded {
task -> if (task.name == "lintVitalReportRelease") task.enabled = false
} }
dependencies { dependencies {
compileOnly 'de.robv.android.xposed:api:82' compileOnly 'de.robv.android.xposed:api:82'
implementation 'com.highcapable.yukihookapi:api:1.0.68' implementation 'com.highcapable.yukihookapi:api:1.0.92'
ksp 'com.highcapable.yukihookapi:ksp-xposed:1.0.68' ksp 'com.highcapable.yukihookapi:ksp-xposed:1.0.92'
implementation "com.github.topjohnwu.libsu:core:3.1.2" implementation "com.github.topjohnwu.libsu:core:3.1.2"
implementation 'androidx.annotation:annotation:1.3.0' implementation 'androidx.annotation:annotation:1.3.0'
implementation 'com.geyifeng.immersionbar:immersionbar:3.2.0' implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.7'
implementation 'com.geyifeng.immersionbar:immersionbar-ktx:3.2.0' implementation 'androidx.core:core-ktx:1.8.0'
implementation 'com.squareup.okhttp3:okhttp:4.9.3' implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'androidx.core:core-ktx:1.7.0' implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

View File

@@ -20,7 +20,6 @@
# hide the original source file name. # hide the original source file name.
#-renamesourcefileattribute SourceFile #-renamesourcefileattribute SourceFile
-dontwarn
-ignorewarnings -ignorewarnings
-optimizationpasses 10 -optimizationpasses 10
-dontusemixedcaseclassnames -dontusemixedcaseclassnames
@@ -35,13 +34,6 @@
-renamesourcefileattribute P -renamesourcefileattribute P
-keepattributes SourceFile,LineNumberTable -keepattributes SourceFile,LineNumberTable
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-assumenosideeffects class kotlin.jvm.internal.Intrinsics { -assumenosideeffects class kotlin.jvm.internal.Intrinsics {
public static *** throwUninitializedProperty(...); public static *** throwUninitializedProperty(...);
public static *** throwUninitializedPropertyAccessException(...); public static *** throwUninitializedPropertyAccessException(...);

View File

@@ -24,24 +24,13 @@
package com.fankes.miui.notify.application package com.fankes.miui.notify.application
import android.app.Application
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication
class MNNApplication : Application() { class MNNApplication : ModuleApplication() {
companion object {
/** 全局静态实例 */
private var context: MNNApplication? = null
/** 调用全局静态实例 */
val appContext get() = context ?: error("App is death")
}
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
/** 设置静态实例 */
context = this
/** 跟随系统夜间模式 */ /** 跟随系统夜间模式 */
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
} }

View File

@@ -1,60 +0,0 @@
/*
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/MIUINativeNotifyIcon
*
* 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.miui.notify.const
import com.fankes.miui.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}_202203292350"
/** 当前模块的版本校验标签 */
const val MODULE_VERSION_VERIFY_TAG = "module_version_verify"
/** 发送通讯广播号标签 - 模块激活状态 */
const val ACTION_MODULE_CHECKING_RECEIVER = "mnn_module_checking_action"
/** 接收通讯广播号标签 - 模块激活状态 */
const val ACTION_MODULE_HANDLER_RECEIVER = "mnn_module_handler_action"
/** 发送通讯广播号标签 - 通知系统界面刷新 */
const val ACTION_REMIND_CHECKING_RECEIVER = "mnn_remind_checking_action"
/** 接收通讯广播号标签 - 通知系统界面刷新 */
const val ACTION_REMIND_HANDLER_RECEIVER = "mnn_remind_handler_action"
}

View File

@@ -32,13 +32,17 @@ object DataConst {
val ENABLE_HIDE_ICON = PrefsData("_hide_icon", false) val ENABLE_HIDE_ICON = PrefsData("_hide_icon", false)
val ENABLE_COLOR_ICON_COMPAT = PrefsData("_color_icon_compat", false) val ENABLE_COLOR_ICON_COMPAT = PrefsData("_color_icon_compat", false)
val ENABLE_NOTIFY_ICON_FIX = PrefsData("_notify_icon_fix", true) val ENABLE_NOTIFY_ICON_FIX = PrefsData("_notify_icon_fix", true)
val ENABLE_NOTIFY_ICON_FORCE_APP_ICON = PrefsData("_notify_icon_force_app_icon", false)
val ENABLE_NOTIFY_ICON_FIX_NOTIFY = PrefsData("_notify_icon_fix_notify", true) val ENABLE_NOTIFY_ICON_FIX_NOTIFY = PrefsData("_notify_icon_fix_notify", true)
val ENABLE_HOOK_STATUS_ICON_COUNT = PrefsData("_enable_hook_status_icon_count", true) val ENABLE_HOOK_STATUS_ICON_COUNT = PrefsData("_enable_hook_status_icon_count", true)
val ENABLE_NOTIFY_ICON_FIX_AUTO = PrefsData("_enable_notify_icon_fix_auto", true) val ENABLE_NOTIFY_ICON_FIX_AUTO = PrefsData("_enable_notify_icon_fix_auto", true)
val NOTIFY_ICON_CORNER = PrefsData("_notify_icon_corner", 15)
val NOTIFY_ICON_DATAS = PrefsData("_notify_icon_datas", "") val NOTIFY_ICON_DATAS = PrefsData("_notify_icon_datas", "")
val NOTIFY_ICON_FIX_AUTO_TIME = PrefsData("_notify_icon_fix_auto_time", "07:00") val NOTIFY_ICON_FIX_AUTO_TIME = PrefsData("_notify_icon_fix_auto_time", "07:00")
val HOOK_STATUS_ICON_COUNT = PrefsData("_hook_status_icon_count", 5) val HOOK_STATUS_ICON_COUNT = PrefsData("_hook_status_icon_count", 5)
val IGNORED_ANDROID_VERSION_TO_LOW = PrefsData("_ignored_android_version_to_low", false)
val SOURCE_SYNC_WAY = PrefsData("_rule_source_sync_way", HookConst.TYPE_SOURCE_SYNC_WAY_1) 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", "") val SOURCE_SYNC_WAY_CUSTOM_URL = PrefsData("_rule_source_sync_way_custom_url", "")
} }

View File

@@ -33,10 +33,10 @@ import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed
import com.highcapable.yukihookapi.hook.factory.configs import com.highcapable.yukihookapi.hook.factory.configs
import com.highcapable.yukihookapi.hook.factory.encase import com.highcapable.yukihookapi.hook.factory.encase
import com.highcapable.yukihookapi.hook.log.loggerW import com.highcapable.yukihookapi.hook.log.loggerW
import com.highcapable.yukihookapi.hook.xposed.proxy.YukiHookXposedInitProxy import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit
@InjectYukiHookWithXposed @InjectYukiHookWithXposed(isUsingResourcesHook = false)
class HookEntry : YukiHookXposedInitProxy { class HookEntry : IYukiHookXposedInit {
override fun onInit() = configs { override fun onInit() = configs {
debugTag = "MIUINativeNotifyIcon" debugTag = "MIUINativeNotifyIcon"
@@ -44,17 +44,19 @@ class HookEntry : YukiHookXposedInitProxy {
} }
override fun onHook() = encase { override fun onHook() = encase {
when { loadApp(SYSTEMUI_PACKAGE_NAME) {
/** 不是 MIUI 系统停止 Hook */ when {
isNotMIUI -> loggerW(msg = "Aborted Hook -> This System is not MIUI") /** 不是 MIUI 系统停止 Hook */
/** 系统版本低于 Android P 停止 Hook */ isNotMIUI -> loggerW(msg = "Aborted Hook -> This System is not MIUI")
isLowerAndroidP -> loggerW(msg = "Aborted Hook -> This System is lower than Android P") /** 系统版本低于 Android P 停止 Hook */
/** 不是支持的 MIUI 系统停止 Hook */ isLowerAndroidP -> loggerW(msg = "Aborted Hook -> This System is lower than Android P")
isNotSupportMiuiVersion -> loggerW(msg = "Aborted Hook -> This MIUI Version $miuiVersion not supported") /** 不是支持的 MIUI 系统停止 Hook */
/** Hook 被手动关闭停止 Hook */ isNotSupportMiuiVersion -> loggerW(msg = "Aborted Hook -> This MIUI Version $miuiVersion not supported")
prefs.get(DataConst.ENABLE_MODULE).not() -> loggerW(msg = "Aborted Hook -> Hook Closed") /** Hook 被手动关闭停止 Hook */
/** 开始 Hook */ prefs.get(DataConst.ENABLE_MODULE).not() -> loggerW(msg = "Aborted Hook -> Hook Closed")
else -> loadApp(SYSTEMUI_PACKAGE_NAME, SystemUIHooker()) /** 开始 Hook */
else -> loadHooker(SystemUIHooker)
}
} }
} }
} }

View File

@@ -20,14 +20,14 @@
* *
* This file is Created by fankes on 2022/3/25. * This file is Created by fankes on 2022/3/25.
*/ */
@file:Suppress("StaticFieldLeak")
package com.fankes.miui.notify.hook.entity package com.fankes.miui.notify.hook.entity
import android.app.NotificationManager import android.app.NotificationManager
import android.app.WallpaperManager import android.app.WallpaperManager
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
import android.graphics.Outline import android.graphics.Outline
@@ -35,14 +35,16 @@ import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon import android.graphics.drawable.Icon
import android.os.Build import android.os.Build
import android.os.SystemClock
import android.service.notification.StatusBarNotification import android.service.notification.StatusBarNotification
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewOutlineProvider import android.view.ViewOutlineProvider
import android.widget.ImageView import android.widget.ImageView
import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toBitmap
import androidx.core.view.children
import androidx.core.view.isVisible
import com.fankes.miui.notify.bean.IconDataBean import com.fankes.miui.notify.bean.IconDataBean
import com.fankes.miui.notify.const.Const
import com.fankes.miui.notify.data.DataConst import com.fankes.miui.notify.data.DataConst
import com.fankes.miui.notify.hook.HookConst.SYSTEMUI_PACKAGE_NAME import com.fankes.miui.notify.hook.HookConst.SYSTEMUI_PACKAGE_NAME
import com.fankes.miui.notify.hook.factory.isAppNotifyHookAllOf import com.fankes.miui.notify.hook.factory.isAppNotifyHookAllOf
@@ -52,12 +54,10 @@ import com.fankes.miui.notify.utils.drawable.drawabletoolbox.DrawableBuilder
import com.fankes.miui.notify.utils.factory.* import com.fankes.miui.notify.utils.factory.*
import com.fankes.miui.notify.utils.tool.BitmapCompatTool import com.fankes.miui.notify.utils.tool.BitmapCompatTool
import com.fankes.miui.notify.utils.tool.IconAdaptationTool import com.fankes.miui.notify.utils.tool.IconAdaptationTool
import com.fankes.miui.notify.utils.tool.SystemUITool
import com.highcapable.yukihookapi.hook.bean.VariousClass import com.highcapable.yukihookapi.hook.bean.VariousClass
import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
import com.highcapable.yukihookapi.hook.factory.field import com.highcapable.yukihookapi.hook.factory.*
import com.highcapable.yukihookapi.hook.factory.hasField
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.loggerD
import com.highcapable.yukihookapi.hook.log.loggerW import com.highcapable.yukihookapi.hook.log.loggerW
import com.highcapable.yukihookapi.hook.type.android.* import com.highcapable.yukihookapi.hook.type.android.*
@@ -67,77 +67,81 @@ import com.highcapable.yukihookapi.hook.type.java.IntType
/** /**
* 系统界面核心 Hook 类 * 系统界面核心 Hook 类
*/ */
class SystemUIHooker : YukiBaseHooker() { object SystemUIHooker : YukiBaseHooker() {
companion object { /** MIUI 新版本存在的类 */
private const val SystemUIApplicationClass = "$SYSTEMUI_PACKAGE_NAME.SystemUIApplication"
/** MIUI 新版本存在的类 */ /** MIUI 新版本存在的类 */
private const val SystemUIApplicationClass = "$SYSTEMUI_PACKAGE_NAME.SystemUIApplication" private const val NotificationHeaderViewWrapperInjectorClass =
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.wrapper.NotificationHeaderViewWrapperInjector"
/** MIUI 新版本存在的类 */ /** MIUI 新版本存在的类 */
private const val NotificationHeaderViewWrapperInjectorClass = private const val MiuiNotificationViewWrapperClass =
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.wrapper.NotificationHeaderViewWrapperInjector" "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.wrapper.MiuiNotificationViewWrapper"
/** MIUI 新版本存在的类 */ /** MIUI 新版本存在的类 */
private const val MiuiNotificationViewWrapperClass = private const val MiuiNotificationChildrenContainerClass =
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.wrapper.MiuiNotificationViewWrapper" "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.stack.MiuiNotificationChildrenContainer"
/** MIUI 新版本存在的类 */ /** 原生存在的类 */
private const val MiuiNotificationChildrenContainerClass = private const val NotificationChildrenContainerClass =
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.stack.MiuiNotificationChildrenContainer" "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.stack.NotificationChildrenContainer"
/** 原生存在的类 */ /** 原生存在的类 */
private const val NotificationChildrenContainerClass = private const val ContrastColorUtilClass = "com.android.internal.util.ContrastColorUtil"
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.stack.NotificationChildrenContainer"
/** 原生存在的类 */ /** 原生存在的类 */
private const val ContrastColorUtilClass = "com.android.internal.util.ContrastColorUtil" private const val StatusBarIconViewClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.StatusBarIconView"
/** 原生存在的类 */ /** 原生存在的类 */
private const val StatusBarIconViewClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.StatusBarIconView" private const val NotificationIconContainerClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.phone.NotificationIconContainer"
/** 原生存在的类 */ /** 原生存在的类 */
private const val NotificationIconContainerClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.phone.NotificationIconContainer" private const val PluginManagerImplClass = "$SYSTEMUI_PACKAGE_NAME.shared.plugins.PluginManagerImpl"
/** 原生存在的类 */ /** 根据多个版本存在不同的包名相同的类 */
private const val PluginManagerImplClass = "$SYSTEMUI_PACKAGE_NAME.shared.plugins.PluginManagerImpl" private val MiuiClockClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.views.MiuiClock",
"$SYSTEMUI_PACKAGE_NAME.statusbar.policy.MiuiClock",
"$SYSTEMUI_PACKAGE_NAME.statusbar.policy.Clock"
)
/** 根据多个版本存在不同的包名相同的类 */ /** 根据多个版本存在不同的包名相同的类 */
private val MiuiClockClass = VariousClass( private val StatusBarNotificationPresenterClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.views.MiuiClock", "$SYSTEMUI_PACKAGE_NAME.statusbar.phone.StatusBarNotificationPresenter",
"$SYSTEMUI_PACKAGE_NAME.statusbar.policy.MiuiClock" "$SYSTEMUI_PACKAGE_NAME.statusbar.phone.StatusBar"
) )
/** 根据多个版本存在不同的包名相同的类 */ /** 根据多个版本存在不同的包名相同的类 */
private val ExpandableNotificationRowClass = VariousClass( private val ExpandableNotificationRowClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.ExpandableNotificationRow", "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.ExpandableNotificationRow",
"$SYSTEMUI_PACKAGE_NAME.statusbar.ExpandableNotificationRow" "$SYSTEMUI_PACKAGE_NAME.statusbar.ExpandableNotificationRow"
) )
/** 根据多个版本存在不同的包名相同的类 */ /** 根据多个版本存在不同的包名相同的类 */
private val NotificationViewWrapperClass = VariousClass( private val NotificationViewWrapperClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.wrapper.NotificationViewWrapper", "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.wrapper.NotificationViewWrapper",
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationViewWrapper" "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationViewWrapper"
) )
/** 根据多个版本存在不同的包名相同的类 */ /** 根据多个版本存在不同的包名相同的类 */
private val NotificationHeaderViewWrapperClass = VariousClass( private val NotificationHeaderViewWrapperClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper", "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper",
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationHeaderViewWrapper" "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationHeaderViewWrapper"
) )
/** 根据多个版本存在不同的包名相同的类 */ /** 根据多个版本存在不同的包名相同的类 */
private val NotificationUtilClass = VariousClass( private val NotificationUtilClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationUtil", "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationUtil",
"$SYSTEMUI_PACKAGE_NAME.miui.statusbar.notification.NotificationUtil" "$SYSTEMUI_PACKAGE_NAME.miui.statusbar.notification.NotificationUtil"
) )
/** 根据多个版本存在不同的包名相同的类 */ /** 根据多个版本存在不同的包名相同的类 */
private val ExpandedNotificationClass = VariousClass( private val ExpandedNotificationClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.ExpandedNotification", "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.ExpandedNotification",
"$SYSTEMUI_PACKAGE_NAME.miui.statusbar.ExpandedNotification" "$SYSTEMUI_PACKAGE_NAME.miui.statusbar.ExpandedNotification"
) )
}
/** 缓存的通知图标优化数组 */ /** 缓存的通知图标优化数组 */
private var iconDatas = ArrayList<IconDataBean>() private var iconDatas = ArrayList<IconDataBean>()
@@ -148,80 +152,15 @@ class SystemUIHooker : YukiBaseHooker() {
/** 是否已经使用过缓存刷新功能 */ /** 是否已经使用过缓存刷新功能 */
private var isUsingCachingMethod = false private var isUsingCachingMethod = false
/** 缓存的状态栏小图标实例 */ /** 状态栏通知图标容器 */
private var statusBarIconViews = HashSet<ImageView>() private var notificationIconContainer: ViewGroup? = null
/** 缓存的通知小图标包装纸实例 */ /** 通知栏通知控制器 */
private var notificationViewWrappers = HashSet<Any>() private var notificationPresenter: Any? = null
/** MIUI 样式下的缓存的通知小图标包装纸实例 */
private var miuiNotificationViewWrappers = HashSet<Any>()
/** MIUI 样式下的缓存的通知小图标折叠通知实例 */
private var miuiNotificationChildrenContainers = HashSet<ViewGroup>()
/** 仅监听一次主题壁纸颜色变化 */ /** 仅监听一次主题壁纸颜色变化 */
private var isWallpaperColorListenerSetUp = false private var isWallpaperColorListenerSetUp = 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 是否判断启用通知功能 - 默认:是 * @param isHooking 是否判断启用通知功能 - 默认:是
@@ -260,27 +199,6 @@ class SystemUIHooker : YukiBaseHooker() {
private val hasHandleHeaderViews private val hasHandleHeaderViews
get() = safeOfFalse { NotificationHeaderViewWrapperClass.clazz.hasMethod { name = "handleHeaderViews" } } get() = safeOfFalse { NotificationHeaderViewWrapperClass.clazz.hasMethod { name = "handleHeaderViews" } }
/**
* 获取是否存在忽略图标色彩处理的方法
* @return [Boolean]
*/
private val hasIgnoreStatusBarIconColor
get() = safeOfFalse {
NotificationUtilClass.clazz.hasMethod {
name = "ignoreStatusBarIconColor"
param(ExpandedNotificationClass.clazz)
}
}
/**
* 获取当前通知栏的样式
*
* - ❗新版本可能不存在这个方法
* @return [Boolean]
*/
private val isShowMiuiStyle
get() = safeOfFalse { NotificationUtilClass.clazz.method { name = "showMiuiStyle" }.get().invoke() ?: false }
/** /**
* 处理为圆角图标 * 处理为圆角图标
* @return [Drawable] * @return [Drawable]
@@ -360,13 +278,25 @@ class SystemUIHooker : YukiBaseHooker() {
return xmsfPkg.ifBlank { targetPkg.ifBlank { packageName } } return xmsfPkg.ifBlank { targetPkg.ifBlank { packageName } }
} }
/**
* 是否为 MIUI 样式通知栏 - 旧版 - 新版一律返回 false
* @return [Boolean]
*/
private val isShowMiuiStyle get() = NotificationUtilClass.clazz.method { name = "showMiuiStyle" }.ignoredError().get().boolean()
/**
* 是否没有单独的 MIUI 通知栏样式
* @return [Boolean]
*/
private val isNotHasAbsoluteMiuiStyle get() = MiuiNotificationViewWrapperClass.hasClass.not()
/** /**
* 获取全局上下文 * 获取全局上下文
* @return [Context] or null * @return [Context] or null
*/ */
private val globalContext private val globalContext
get() = safeOfNull { get() = safeOfNull {
SystemUIApplicationClass.clazz.method { name = "getContext" }.ignoredError().get().invoke<Context>() SystemUIApplicationClass.clazz.method { name = "getContext" }.ignoredError().get().invoke<Context?>() ?: appContext
} }
/** /**
@@ -385,12 +315,12 @@ class SystemUIHooker : YukiBaseHooker() {
/** 刷新状态栏小图标 */ /** 刷新状态栏小图标 */
private fun refreshStatusBarIcons() = runInSafe { private fun refreshStatusBarIcons() = runInSafe {
StatusBarIconViewClass.clazz.field { name = "mNotification" }.also { result -> StatusBarIconViewClass.clazz.field { name = "mNotification" }.also { result ->
statusBarIconViews.takeIf { it.isNotEmpty() }?.forEach { notificationIconContainer?.children?.forEach {
/** 得到通知实例 */ /** 得到通知实例 */
val nf = result.get(it).cast<StatusBarNotification>() ?: return val nf = result.get(it).cast<StatusBarNotification>() ?: return
/** 刷新状态栏图标 */ /** 刷新状态栏图标 */
compatStatusIcon(it.context, nf, nf.notification.smallIcon.loadDrawable(it.context)).also { pair -> compatStatusIcon(it.context, nf, nf.notification.smallIcon.loadDrawable(it.context)).also { pair ->
pair.first.let { e -> it.setImageDrawable(e) } pair.first.let { e -> (it as? ImageView?)?.setImageDrawable(e) }
} }
} }
} }
@@ -398,19 +328,11 @@ class SystemUIHooker : YukiBaseHooker() {
/** 刷新通知小图标 */ /** 刷新通知小图标 */
private fun refreshNotificationIcons() = runInSafe { private fun refreshNotificationIcons() = runInSafe {
(if (hasHandleHeaderViews) notificationPresenter?.current {
NotificationHeaderViewWrapperClass.clazz.method { name = "handleHeaderViews" } method {
else NotificationHeaderViewWrapperClass.clazz.method { name = "resolveHeaderViews" }).also { result -> name = "updateNotificationsOnDensityOrFontScaleChanged"
notificationViewWrappers.takeIf { it.isNotEmpty() }?.forEach { result.get(it).call() } emptyParam()
} }.call()
MiuiNotificationViewWrapperClass.clazz.method { name = "handleViews" }.ignoredError().also { result ->
miuiNotificationViewWrappers.takeIf { it.isNotEmpty() }?.forEach { result.get(it).call() }
}
MiuiNotificationChildrenContainerClass.clazz.method {
name = "updateAppIcon"
param(BooleanType)
}.ignoredError().also { result ->
miuiNotificationChildrenContainers.takeIf { it.isNotEmpty() }?.forEach { result.get(it).call(true) }
} }
} }
@@ -480,6 +402,35 @@ class SystemUIHooker : YukiBaseHooker() {
isExpanded: Boolean = true, isExpanded: Boolean = true,
isUseAndroid12Style: Boolean = isUpperOfAndroidS, isUseAndroid12Style: Boolean = isUpperOfAndroidS,
) = runInSafe(msg = "compatNotifyIcon") { ) = runInSafe(msg = "compatNotifyIcon") {
/**
* 设置默认通知图标
* @param drawable 通知图标
*/
fun setDefaultNotifyIcon(drawable: Drawable?) {
iconImageView.apply {
/** 重新设置图标 */
setImageDrawable(drawable)
/** 设置裁切到边界 */
clipToOutline = true
/** 设置一个圆角轮廓裁切 */
outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View, out: Outline) {
out.setRoundRect(
0, 0,
view.width, view.height, 5.dpFloat(context)
)
}
}
if (isUseAndroid12Style) {
/** 清除原生的背景边距 */
setPadding(0, 0, 0, 0)
/** 清除原生的主题色背景圆圈颜色 */
background = null
}
/** 清除遮罩颜色 */
colorFilter = null
}
}
/** 获取通知对象 - 由于 MIUI 的版本迭代不规范性可能是空的 */ /** 获取通知对象 - 由于 MIUI 的版本迭代不规范性可能是空的 */
expandedNf?.let { notifyInstance -> expandedNf?.let { notifyInstance ->
@@ -489,6 +440,9 @@ class SystemUIHooker : YukiBaseHooker() {
/** 旧版风格反色 */ /** 旧版风格反色 */
val oldStyle = if (context.isNotSystemInDarkMode) 0xFF707070.toInt() else Color.WHITE val oldStyle = if (context.isNotSystemInDarkMode) 0xFF707070.toInt() else Color.WHITE
/** 通知图标边框圆角大小 */
val iconCorner = prefs.get(DataConst.NOTIFY_ICON_CORNER)
/** 通知图标原始颜色 */ /** 通知图标原始颜色 */
val iconColor = notifyInstance.notification.color val iconColor = notifyInstance.notification.color
@@ -523,62 +477,47 @@ class SystemUIHooker : YukiBaseHooker() {
/** 打印日志 */ /** 打印日志 */
printLogcat(tag = "NotifyIcon", context, notifyInstance, isCustom = customIcon != null, isGrayscaleIcon) printLogcat(tag = "NotifyIcon", context, notifyInstance, isCustom = customIcon != null, isGrayscaleIcon)
/** 处理自定义通知图标优化 */ /** 处理自定义通知图标优化 */
if (customIcon != null) iconImageView.apply { when {
/** 设置不要裁切到边界 */ prefs.get(DataConst.ENABLE_NOTIFY_ICON_FORCE_APP_ICON) && isEnableHookColorNotifyIcon(isHooking = false) ->
clipToOutline = false setDefaultNotifyIcon(context.findAppIcon(notifyInstance.nfPkgName))
/** 设置自定义小图标 */ customIcon != null -> iconImageView.apply {
setImageBitmap(customIcon)
/** 上色 */
setColorFilter(if (isUseAndroid12Style || customIconColor == 0) supportColor else customIconColor)
/** Android 12 设置图标外圈颜色 */
if (isUseAndroid12Style && customIconColor != 0)
background = DrawableBuilder()
.rounded()
.solidColor(if (context.isSystemInDarkMode) customIconColor.brighter else customIconColor)
.build()
/** 设置原生的背景边距 */
if (isUseAndroid12Style) setPadding(4.dp(context), 4.dp(context), 4.dp(context), 4.dp(context))
} else {
/** 重新设置图标 - 防止系统更改它 */
iconImageView.setImageDrawable(iconDrawable)
/** 判断如果是灰度图标就给他设置一个白色颜色遮罩 */
if (isGrayscaleIcon) iconImageView.apply {
/** 设置不要裁切到边界 */ /** 设置不要裁切到边界 */
clipToOutline = false clipToOutline = false
/** 设置图标着色 */ /** 设置自定义小图标 */
setColorFilter(supportColor) setImageBitmap(customIcon)
/** 上色 */
setColorFilter(if (isUseAndroid12Style || customIconColor == 0) supportColor else customIconColor)
/** Android 12 设置图标外圈颜色 */ /** Android 12 设置图标外圈颜色 */
(if (hasIconColor) iconColor else context.systemAccentColor).also { if (isUseAndroid12Style && customIconColor != 0)
if (isUseAndroid12Style) background = DrawableBuilder()
background = DrawableBuilder() .rectangle()
.rounded() .cornerRadius(iconCorner.dp(context))
.solidColor(if (context.isSystemInDarkMode) it.brighter else it) .solidColor(if (context.isSystemInDarkMode) customIconColor.brighter else customIconColor)
.build() .build()
}
/** 设置原生的背景边距 */ /** 设置原生的背景边距 */
if (isUseAndroid12Style) setPadding(4.dp(context), 4.dp(context), 4.dp(context), 4.dp(context)) if (isUseAndroid12Style) setPadding(4.dp(context), 4.dp(context), 4.dp(context), 4.dp(context))
} else iconImageView.apply { }
/** 重新设置图标 */ else -> {
setImageDrawable(notifyInstance.compatPushingIcon(context, iconDrawable)) /** 重新设置图标 - 防止系统更改它 */
/** 设置裁切到边界 */ iconImageView.setImageDrawable(iconDrawable)
clipToOutline = true /** 判断如果是灰度图标就给他设置一个白色颜色遮罩 */
/** 设置一个圆角轮廓裁切 */ if (isGrayscaleIcon) iconImageView.apply {
outlineProvider = object : ViewOutlineProvider() { /** 设置不要裁切到边界 */
override fun getOutline(view: View, out: Outline) { clipToOutline = false
out.setRoundRect( /** 设置图标着色 */
0, 0, setColorFilter(supportColor)
view.width, view.height, 5.dpFloat(context) /** Android 12 设置图标外圈颜色 */
) (if (hasIconColor) iconColor else context.systemAccentColor).also {
if (isUseAndroid12Style)
background = DrawableBuilder()
.rectangle()
.cornerRadius(iconCorner.dp(context))
.solidColor(if (context.isSystemInDarkMode) it.brighter else it)
.build()
} }
} /** 设置原生的背景边距 */
if (isUseAndroid12Style) { if (isUseAndroid12Style) setPadding(4.dp(context), 4.dp(context), 4.dp(context), 4.dp(context))
/** 清除原生的背景边距 */ } else setDefaultNotifyIcon(notifyInstance.compatPushingIcon(context, iconDrawable))
setPadding(0, 0, 0, 0)
/** 清除原生的主题色背景圆圈颜色 */
background = null
}
/** 清除遮罩颜色 */
colorFilter = null
} }
} }
} }
@@ -646,11 +585,42 @@ class SystemUIHooker : YukiBaseHooker() {
.get(this).call()?.let { .get(this).call()?.let {
it.javaClass.method { it.javaClass.method {
name = "getSbn" name = "getSbn"
}.get(it).invoke<StatusBarNotification>() }.ignoredError().get(it).invoke<StatusBarNotification>()
} ?: ExpandableNotificationRowClass.clazz } ?: ExpandableNotificationRowClass.clazz
.method { name = "getStatusBarNotification" } .method { name = "getStatusBarNotification" }
.get(NotificationViewWrapperClass.clazz.field { name = "mRow" }.get(this).self) .get(this).invoke<StatusBarNotification>()
.invoke<StatusBarNotification>()
/**
* 根据当前 [ImageView] 的父布局克隆一个新的 [ImageView]
* @param initiate 新的 [ImageView] 方法体 - 失败会返回当前 [ImageView]
*/
private fun ImageView.clone(initiate: ImageView.() -> Unit) {
(parent as? ViewGroup?)?.also { parent ->
/** 将之前的 [ImageView] 调为全透明且隐藏 */
alpha = 0f
isVisible = false
/** 将内容清空 */
setImageDrawable(null)
setBackgroundColor(Color.TRANSPARENT)
/** 创建一个傀儡 */
parent.findViewWithTag<ImageView?>("new_view").also { base ->
if (base == null) parent.addView(ImageView(context).also { new ->
new.tag = "new_view"
/** 克隆它的 [ViewGroup.LayoutParams] */
new.layoutParams = layoutParams
initiate(new)
}) else initiate(base)
}
} ?: initiate(this)
}
/** 注册 */
private fun register() {
/** 解锁后重新刷新状态栏图标防止系统重新设置它 */
onAppLifecycle { registerReceiver(Intent.ACTION_USER_PRESENT) { _, _ -> if (isUsingCachingMethod) refreshStatusBarIcons() } }
/** 刷新图标缓存 */
SystemUITool.Host.onRefreshSystemUI(param = this) { recachingPrefs(it) }
}
/** 缓存图标数据 */ /** 缓存图标数据 */
private fun cachingIconDatas() { private fun cachingIconDatas() {
@@ -666,17 +636,25 @@ class SystemUIHooker : YukiBaseHooker() {
/** /**
* 刷新缓存数据 * 刷新缓存数据
* @param isRefreshCacheOnly 仅刷新缓存不刷新图标和通知改变 - 默认:否 * @param isRefreshCacheOnly 仅刷新缓存不刷新图标和通知改变 - 默认:否
* @return [Boolean] 是否成功
*/ */
private fun recachingPrefs(isRefreshCacheOnly: Boolean = false) { private fun recachingPrefs(isRefreshCacheOnly: Boolean = false): Boolean {
isUsingCachingMethod = true /** 必要的延迟防止 Sp 存储不刷新 */
prefs.clearCache() SystemClock.sleep(100)
cachingIconDatas() /** 获取可读写状态 */
if (isRefreshCacheOnly) return return prefs.isXSharePrefsReadable.also {
refreshStatusBarIcons() isUsingCachingMethod = true
refreshNotificationIcons() prefs.clearCache()
cachingIconDatas()
if (isRefreshCacheOnly) return@also
refreshStatusBarIcons()
refreshNotificationIcons()
}
} }
override fun onHook() { override fun onHook() {
/** 注册 */
register()
/** 缓存图标数据 */ /** 缓存图标数据 */
cachingIconDatas() cachingIconDatas()
/** 注入 MIUI 自己增加的一个工具类 */ /** 注入 MIUI 自己增加的一个工具类 */
@@ -685,38 +663,34 @@ class SystemUIHooker : YukiBaseHooker() {
injectMember { injectMember {
method { method {
name = "shouldSubstituteSmallIcon" name = "shouldSubstituteSmallIcon"
param(ExpandedNotificationClass.clazz) param(ExpandedNotificationClass)
} }
/** replaceToFalse()
* MIUI 12 在非原生样式下 MIPUSH 的图标着色异常
* 所以判断是 MIUI 样式就停止 Hook 状态栏图标
*/
replaceAny { hasIgnoreStatusBarIconColor.not() && isShowMiuiStyle }
} }
/** 强制回写系统的状态栏图标样式为原生 */ /** 强制回写系统的状态栏图标样式为原生 */
injectMember { injectMember {
var isUseLegacy = false var isUseLegacy = false
method { method {
name = "getSmallIcon" name = "getSmallIcon"
param(ExpandedNotificationClass.clazz, IntType) param(ExpandedNotificationClass, IntType)
}.remedys { }.remedys {
method { method {
name = "getSmallIcon" name = "getSmallIcon"
param(ExpandedNotificationClass.clazz) param(ExpandedNotificationClass)
} }
method { method {
name = "getSmallIcon" name = "getSmallIcon"
param(ContextClass, ExpandedNotificationClass.clazz) param(ContextClass, ExpandedNotificationClass)
}.onFind { isUseLegacy = true } }.onFind { isUseLegacy = true }
} }
afterHook { afterHook {
(globalContext ?: firstArgs())?.also { context -> (globalContext ?: args().first().cast())?.also { context ->
val expandedNf = args(if (isUseLegacy) 1 else 0).cast<StatusBarNotification?>() val expandedNf = args(if (isUseLegacy) 1 else 0).cast<StatusBarNotification?>()
/** Hook 状态栏小图标 */ /** Hook 状态栏小图标 */
compatStatusIcon( compatStatusIcon(
context = context, context = context,
expandedNf, expandedNf,
(result as Icon).loadDrawable(context) result<Icon>()?.loadDrawable(context)
).also { pair -> if (pair.second) result = Icon.createWithBitmap(pair.first?.toBitmap()) } ).also { pair -> if (pair.second) result = Icon.createWithBitmap(pair.first?.toBitmap()) }
} }
} }
@@ -740,16 +714,14 @@ class SystemUIHooker : YukiBaseHooker() {
* MIUI 12 进行单独判断 * MIUI 12 进行单独判断
*/ */
field { name = "mCurrentSetColor" }.get(instance).int().also { color -> field { name = "mCurrentSetColor" }.get(instance).int().also { color ->
if (hasIgnoreStatusBarIconColor) { alpha = if (color.isWhite) 0.95f else 0.8f
alpha = if (color.isWhite) 0.95f else 0.8f setColorFilter(if (color.isWhite) color else Color.BLACK)
setColorFilter(if (color.isWhite) color else Color.BLACK)
} else setColorFilter(color)
} }
} }
} }
} }
} }
/** 记录实例 */ /** 注册广播 */
injectMember { injectMember {
method { method {
name = "setNotification" name = "setNotification"
@@ -757,26 +729,29 @@ class SystemUIHooker : YukiBaseHooker() {
}.remedys { }.remedys {
method { method {
name = "setNotification" name = "setNotification"
param(ExpandedNotificationClass.clazz) param(ExpandedNotificationClass)
} }
} }
afterHook { afterHook {
if (firstArgs != null) instance<ImageView>().also { /** 注册壁纸颜色监听 */
/** 注册壁纸颜色监听 */ if (args().first().any() != null) instance<ImageView>().also { registerWallpaperColorChanged(it) }
registerWallpaperColorChanged(it)
/** 注册广播 */
registerReceiver(it.context)
/** 缓存实例 */
statusBarIconViews.add(it)
}
} }
} }
} }
/** 注入通知控制器实例 */
StatusBarNotificationPresenterClass.hook {
injectMember {
allConstructors()
afterHook { notificationPresenter = instance }
}
}
/** 注入状态栏通知图标容器实例 */ /** 注入状态栏通知图标容器实例 */
NotificationIconContainerClass.hook { NotificationIconContainerClass.hook {
injectMember { injectMember {
method { name = "calculateIconTranslations" } method { name = "calculateIconTranslations" }
afterHook { afterHook {
/** 缓存实例 */
notificationIconContainer = instance<ViewGroup>()
/** 修复部分开发版状态栏图标只能显示一个的问题 */ /** 修复部分开发版状态栏图标只能显示一个的问题 */
when (miuiIncrementalVersion.lowercase()) { when (miuiIncrementalVersion.lowercase()) {
"22.3.14", "22.3.15", "22.3.16", "v13.0.1.1.16.dev", "22.3.18" -> "22.3.14", "22.3.15", "22.3.16", "v13.0.1.1.16.dev", "22.3.18" ->
@@ -793,14 +768,14 @@ class SystemUIHooker : YukiBaseHooker() {
.get(instance).set(prefs.get(DataConst.HOOK_STATUS_ICON_COUNT) .get(instance).set(prefs.get(DataConst.HOOK_STATUS_ICON_COUNT)
.let { if (it in 0..100) it else 5 }) .let { if (it in 0..100) it else 5 })
} }
} }.by { NotificationIconContainerClass.clazz.hasField { name = "MAX_STATIC_ICONS" } }
/** 旧版方法 - 新版不存在 */ /** 旧版方法 - 新版不存在 */
injectMember { injectMember {
method { method {
name = "setMaxStaticIcons" name = "setMaxStaticIcons"
param(IntType) param(IntType)
} }
beforeHook { isShowNotificationIcons = (firstArgs<Int>() ?: 0) > 0 } beforeHook { isShowNotificationIcons = args().first().int() > 0 }
}.ignoredNoSuchMemberFailure() }.ignoredNoSuchMemberFailure()
/** 新版方法 - 旧版不存在 */ /** 新版方法 - 旧版不存在 */
injectMember { injectMember {
@@ -808,9 +783,9 @@ class SystemUIHooker : YukiBaseHooker() {
name = "miuiShowNotificationIcons" name = "miuiShowNotificationIcons"
param(BooleanType) param(BooleanType)
} }
beforeHook { isShowNotificationIcons = firstArgs<Boolean>() ?: false } beforeHook { isShowNotificationIcons = args().first().boolean() }
}.ignoredNoSuchMemberFailure() }.ignoredNoSuchMemberFailure()
}.by { NotificationIconContainerClass.clazz.hasField { name = "MAX_STATIC_ICONS" } } }
/** 注入原生通知包装纸实例 */ /** 注入原生通知包装纸实例 */
NotificationHeaderViewWrapperClass.hook { NotificationHeaderViewWrapperClass.hook {
/** 修复下拉通知图标自动设置回 APP 图标的方法 */ /** 修复下拉通知图标自动设置回 APP 图标的方法 */
@@ -819,6 +794,9 @@ class SystemUIHooker : YukiBaseHooker() {
method { name = "handleHeaderViews" } method { name = "handleHeaderViews" }
else method { name = "resolveHeaderViews" } else method { name = "resolveHeaderViews" }
afterHook { afterHook {
/** 忽略较旧版本 - 在没有 MIUI 通知栏样式的时候可能出现奇怪的问题 */
if (isNotHasAbsoluteMiuiStyle && isShowMiuiStyle) return@afterHook
/** 获取小图标 */ /** 获取小图标 */
val iconImageView = val iconImageView =
NotificationHeaderViewWrapperClass.clazz NotificationHeaderViewWrapperClass.clazz
@@ -843,11 +821,6 @@ class SystemUIHooker : YukiBaseHooker() {
compatNotifyIcon(iconImageView.context, expandedNf, iconImageView, isExpanded) compatNotifyIcon(iconImageView.context, expandedNf, iconImageView, isExpanded)
} }
} }
/** 记录实例 */
injectMember {
constructor { param(ContextClass, ViewClass, ExpandableNotificationRowClass.clazz) }
afterHook { notificationViewWrappers.add(instance) }
}
} }
/** 修改 MIUI 风格通知栏的通知图标 */ /** 修改 MIUI 风格通知栏的通知图标 */
MiuiNotificationViewWrapperClass.hook { MiuiNotificationViewWrapperClass.hook {
@@ -855,7 +828,7 @@ class SystemUIHooker : YukiBaseHooker() {
injectMember { injectMember {
method { name = "handleAppIcon" } method { name = "handleAppIcon" }
replaceUnit { replaceUnit {
field { name = "mAppIcon" }.get(instance).cast<ImageView>()?.apply { field { name = "mAppIcon" }.get(instance).cast<ImageView>()?.clone {
compatNotifyIcon( compatNotifyIcon(
context = context, context = context,
expandedNf = instance.getRowPair().second.getSbn(), expandedNf = instance.getRowPair().second.getSbn(),
@@ -865,11 +838,6 @@ class SystemUIHooker : YukiBaseHooker() {
} }
} }
} }
/** 记录实例 */
injectMember {
constructor { param(ContextClass, ViewClass, ExpandableNotificationRowClass.clazz) }
afterHook { miuiNotificationViewWrappers.add(instance) }
}
}.ignoredHookClassNotFoundFailure() }.ignoredHookClassNotFoundFailure()
/** 修改 MIUI 风格通知栏的通知图标 - 折叠通知 */ /** 修改 MIUI 风格通知栏的通知图标 - 折叠通知 */
MiuiNotificationChildrenContainerClass.hook { MiuiNotificationChildrenContainerClass.hook {
@@ -888,22 +856,17 @@ class SystemUIHooker : YukiBaseHooker() {
} }
} }
} }
/** 记录实例 */
injectMember {
constructor { param(ContextClass, AttributeSetClass) }
afterHook { miuiNotificationChildrenContainers.add(instance()) }
}
}.ignoredHookClassNotFoundFailure() }.ignoredHookClassNotFoundFailure()
/** 干掉下拉通知图标自动设置回 APP 图标的方法 */ /** 干掉下拉通知图标自动设置回 APP 图标的方法 */
NotificationHeaderViewWrapperInjectorClass.hook { NotificationHeaderViewWrapperInjectorClass.hook {
injectMember { injectMember {
method { method {
name = "setAppIcon" name = "setAppIcon"
param(ContextClass, ImageViewClass, ExpandedNotificationClass.clazz) param(ContextClass, ImageViewClass, ExpandedNotificationClass)
}.remedys { }.remedys {
method { method {
name = "setAppIcon" name = "setAppIcon"
param(ImageViewClass, ExpandedNotificationClass.clazz) param(ImageViewClass, ExpandedNotificationClass)
} }
} }
intercept() intercept()
@@ -911,7 +874,7 @@ class SystemUIHooker : YukiBaseHooker() {
injectMember { injectMember {
method { method {
name = "resetIconBgAndPaddings" name = "resetIconBgAndPaddings"
param(ImageViewClass, ExpandedNotificationClass.clazz) param(ImageViewClass, ExpandedNotificationClass)
} }
intercept() intercept()
}.ignoredNoSuchMemberFailure() }.ignoredNoSuchMemberFailure()
@@ -924,7 +887,7 @@ class SystemUIHooker : YukiBaseHooker() {
param(ContextClass, IntentClass) param(ContextClass, IntentClass)
} }
afterHook { afterHook {
if (isEnableHookColorNotifyIcon()) (lastArgs as? Intent)?.also { if (isEnableHookColorNotifyIcon()) args().last().cast<Intent>()?.also {
if (it.action.equals(Intent.ACTION_PACKAGE_REPLACED).not() && if (it.action.equals(Intent.ACTION_PACKAGE_REPLACED).not() &&
it.getBooleanExtra(Intent.EXTRA_REPLACING, false) it.getBooleanExtra(Intent.EXTRA_REPLACING, false)
) return@also ) return@also
@@ -934,26 +897,24 @@ class SystemUIHooker : YukiBaseHooker() {
if (iconDatas.takeIf { e -> e.isNotEmpty() } if (iconDatas.takeIf { e -> e.isNotEmpty() }
?.filter { e -> e.packageName == newPkgName } ?.filter { e -> e.packageName == newPkgName }
.isNullOrEmpty() .isNullOrEmpty()
) IconAdaptationTool.pushNewAppSupportNotify(firstArgs()!!, newPkgName) ) IconAdaptationTool.pushNewAppSupportNotify(args().first().cast()!!, newPkgName)
} }
Intent.ACTION_PACKAGE_REMOVED -> Intent.ACTION_PACKAGE_REMOVED ->
IconAdaptationTool.removeNewAppSupportNotify( IconAdaptationTool.removeNewAppSupportNotify(
context = firstArgs()!!, context = args().first().cast()!!,
packageName = it.data?.schemeSpecificPart ?: "" packageName = it.data?.schemeSpecificPart ?: ""
) )
} }
} }
} }
} }
} }.ignoredHookClassNotFoundFailure()
/** 自动检查通知图标优化更新的注入监听 */ /** 自动检查通知图标优化更新的注入监听 */
MiuiClockClass.hook { MiuiClockClass.hook {
injectMember { injectMember {
method { name = "updateTime" } method { name = "updateTime" }.remedys { method { name = "updateClock" } }
afterHook { afterHook {
instance<View>().context.also { instance<View>().context.also {
/** 注册广播 */
registerReceiver(it)
/** 注册定时监听 */ /** 注册定时监听 */
if (isEnableHookColorNotifyIcon() && prefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX_AUTO)) if (isEnableHookColorNotifyIcon() && prefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX_AUTO))
IconAdaptationTool.prepareAutoUpdateIconRule( IconAdaptationTool.prepareAutoUpdateIconRule(
@@ -963,6 +924,6 @@ class SystemUIHooker : YukiBaseHooker() {
} }
} }
} }
}.ignoredHookClassNotFoundFailure() }
} }
} }

View File

@@ -24,10 +24,6 @@
package com.fankes.miui.notify.ui.activity package com.fankes.miui.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 androidx.core.view.isVisible
import com.fankes.miui.notify.R import com.fankes.miui.notify.R
import com.fankes.miui.notify.bean.IconDataBean import com.fankes.miui.notify.bean.IconDataBean
@@ -43,7 +39,7 @@ import com.fankes.miui.notify.ui.activity.base.BaseActivity
import com.fankes.miui.notify.utils.factory.* import com.fankes.miui.notify.utils.factory.*
import com.fankes.miui.notify.utils.tool.IconRuleManagerTool import com.fankes.miui.notify.utils.tool.IconRuleManagerTool
import com.fankes.miui.notify.utils.tool.SystemUITool import com.fankes.miui.notify.utils.tool.SystemUITool
import com.highcapable.yukihookapi.hook.factory.isXposedModuleActive import com.highcapable.yukihookapi.YukiHookAPI
class ConfigureActivity : BaseActivity<ActivityConfigBinding>() { class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
@@ -61,7 +57,7 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
override fun onCreate() { override fun onCreate() {
/** 检查激活状态 */ /** 检查激活状态 */
if (isXposedModuleActive.not()) { if (YukiHookAPI.Status.isXposedModuleActive.not()) {
showDialog { showDialog {
title = "模块没有激活" title = "模块没有激活"
msg = "模块没有激活,你无法使用这里的功能,请先激活模块。" msg = "模块没有激活,你无法使用这里的功能,请先激活模块。"
@@ -85,9 +81,9 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
} }
/** 设置过滤按钮点击事件 */ /** 设置过滤按钮点击事件 */
binding.configTitleFilter.setOnClickListener { binding.configTitleFilter.setOnClickListener {
showDialog { showDialog<DiaIconFilterBinding> {
title = "按条件过滤" title = "按条件过滤"
val editText = bind<DiaIconFilterBinding>().diaIconFilterInputEdit.apply { binding.iconFiltersEdit.apply {
requestFocus() requestFocus()
invalidate() invalidate()
if (filterText.isNotBlank()) { if (filterText.isNotBlank()) {
@@ -96,8 +92,8 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
} }
} }
confirmButton { confirmButton {
if (editText.text.toString().isNotBlank()) { if (binding.iconFiltersEdit.text.toString().isNotBlank()) {
filterText = editText.text.toString().trim() filterText = binding.iconFiltersEdit.text.toString().trim()
refreshAdapterResult() refreshAdapterResult()
} else { } else {
toast(msg = "条件不能为空") toast(msg = "条件不能为空")
@@ -116,49 +112,46 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
binding.configTitleSync.setOnClickListener { onStartRefresh() } binding.configTitleSync.setOnClickListener { onStartRefresh() }
/** 设置列表元素和 Adapter */ /** 设置列表元素和 Adapter */
binding.configListView.apply { binding.configListView.apply {
adapter = object : BaseAdapter() { bindAdapter {
onBindDatas { iconDatas }
override fun getCount() = iconDatas.size onBindViews<AdapterConfigBinding> { binding, position ->
iconDatas[position].also { bean ->
override fun getItem(position: Int) = iconDatas[position] binding.adpAppIcon.setImageBitmap(bean.iconBitmap)
(if (bean.iconColor != 0) bean.iconColor else resources.getColor(R.color.colorTextGray)).also { color ->
override fun getItemId(position: Int) = position.toLong() binding.adpAppIcon.setColorFilter(color)
binding.adpAppName.setTextColor(color)
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 binding.adpAppName.text = bean.appName
holder.adpAppPkgName.text = it.packageName binding.adpAppPkgName.text = bean.packageName
holder.adpCbrName.text = "贡献者:" + it.contributorName binding.adpCbrName.text = "贡献者:" + bean.contributorName
isAppNotifyHookOf(it).also { e -> isAppNotifyHookOf(bean).also { e ->
holder.adpAppOpenSwitch.isChecked = e binding.adpAppOpenSwitch.isChecked = e
holder.adpAppAllSwitch.isEnabled = e binding.adpAppAllSwitch.isEnabled = e
} }
holder.adpAppOpenSwitch.setOnCheckedChangeListener { btn, b -> binding.adpAppOpenSwitch.setOnCheckedChangeListener { btn, b ->
if (btn.isPressed.not()) return@setOnCheckedChangeListener if (btn.isPressed.not()) return@setOnCheckedChangeListener
putAppNotifyHookOf(it, b) putAppNotifyHookOf(bean, b)
holder.adpAppAllSwitch.isEnabled = b binding.adpAppAllSwitch.isEnabled = b
SystemUITool.refreshSystemUI(context = this@ConfigureActivity) SystemUITool.refreshSystemUI(context = this@ConfigureActivity)
} }
holder.adpAppAllSwitch.isChecked = isAppNotifyHookAllOf(it) binding.adpAppAllSwitch.isChecked = isAppNotifyHookAllOf(bean)
holder.adpAppAllSwitch.setOnCheckedChangeListener { btn, b -> binding.adpAppAllSwitch.setOnCheckedChangeListener { btn, b ->
if (btn.isPressed.not()) return@setOnCheckedChangeListener if (btn.isPressed.not()) return@setOnCheckedChangeListener
putAppNotifyHookAllOf(it, b) fun saveState() {
SystemUITool.refreshSystemUI(context = this@ConfigureActivity) putAppNotifyHookAllOf(bean, b)
SystemUITool.refreshSystemUI(context = this@ConfigureActivity)
}
if (b) showDialog {
title = "全部替换"
msg = "此功能仅针对严重不遵守规范的 APP 通知图标才需要开启例如APP 推送通知后无法识别出现的黑白块图标。\n\n" +
"此功能在一般情况下请保持关闭并跟随在线规则的配置,并不要随意改变此配置," +
"开启后 APP 的通知图标可能会被规则破坏,你确定还要开启吗?"
confirmButton { saveState() }
cancelButton { btn.isChecked = btn.isChecked.not() }
noCancelable()
} else saveState()
} }
} }
return cView!!
} }
}.apply { }.apply {
setOnItemLongClickListener { _, _, p, _ -> setOnItemLongClickListener { _, _, p, _ ->
@@ -176,7 +169,13 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
} }
/** 设置点击事件 */ /** 设置点击事件 */
binding.configCbrButton.setOnClickListener { binding.configCbrButton.setOnClickListener {
openBrowser(url = "https://github.com/fankes/AndroidNotifyIconAdapt/blob/main/CONTRIBUTING.md") showDialog {
title = "感谢你的贡献"
msg = "通知图标优化名单需要大家的共同维护才能得以完善,请选择你的贡献方式。"
confirmButton(text = "贡献规则") { openBrowser(url = "https://github.com/fankes/AndroidNotifyIconAdapt/blob/main/CONTRIBUTING.md") }
cancelButton(text = "请求适配") { openBrowser(url = "https://github.com/fankes/MIUINativeNotifyIcon/issues/new/choose") }
neutralButton(text = "暂时不用")
}
} }
/** 装载数据 */ /** 装载数据 */
mockLocalData() mockLocalData()

View File

@@ -26,10 +26,11 @@ package com.fankes.miui.notify.ui.activity
import android.content.ComponentName import android.content.ComponentName
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.widget.SeekBar
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.fankes.miui.notify.BuildConfig
import com.fankes.miui.notify.R import com.fankes.miui.notify.R
import com.fankes.miui.notify.const.Const
import com.fankes.miui.notify.data.DataConst import com.fankes.miui.notify.data.DataConst
import com.fankes.miui.notify.databinding.ActivityMainBinding import com.fankes.miui.notify.databinding.ActivityMainBinding
import com.fankes.miui.notify.databinding.DiaStatusIconCountBinding import com.fankes.miui.notify.databinding.DiaStatusIconCountBinding
@@ -38,9 +39,9 @@ import com.fankes.miui.notify.ui.activity.base.BaseActivity
import com.fankes.miui.notify.utils.factory.* import com.fankes.miui.notify.utils.factory.*
import com.fankes.miui.notify.utils.tool.GithubReleaseTool import com.fankes.miui.notify.utils.tool.GithubReleaseTool
import com.fankes.miui.notify.utils.tool.SystemUITool import com.fankes.miui.notify.utils.tool.SystemUITool
import com.highcapable.yukihookapi.hook.factory.isXposedModuleActive import com.fankes.miui.notify.utils.tool.YukiPromoteTool
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.hook.factory.modulePrefs import com.highcapable.yukihookapi.hook.factory.modulePrefs
import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus
class MainActivity : BaseActivity<ActivityMainBinding>() { class MainActivity : BaseActivity<ActivityMainBinding>() {
@@ -50,7 +51,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
internal var isActivityLive = false internal var isActivityLive = false
/** 模块版本 */ /** 模块版本 */
private const val moduleVersion = Const.MODULE_VERSION_NAME private const val moduleVersion = BuildConfig.VERSION_NAME
/** 预发布的版本标识 */ /** 预发布的版本标识 */
private const val pendingFlag = "" private const val pendingFlag = ""
@@ -105,7 +106,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
noCancelable() noCancelable()
} }
/** 判断是否 Hook */ /** 判断是否 Hook */
isXposedModuleActive -> { YukiHookAPI.Status.isXposedModuleActive -> {
if (IconPackParams(context = this).iconDatas.isEmpty() && modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX)) if (IconPackParams(context = this).iconDatas.isEmpty() && modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX))
showDialog { showDialog {
title = "配置通知图标优化名单" title = "配置通知图标优化名单"
@@ -123,6 +124,16 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
cancelButton() cancelButton()
noCancelable() noCancelable()
} }
if (isLowerAndroidR && modulePrefs.get(DataConst.IGNORED_ANDROID_VERSION_TO_LOW).not())
showDialog {
title = "Android 版本过低"
msg = "你当前使用的 Android 版本过低,模块的部分功能可能会发生问题," +
"由于设备有限,无法逐一调试,若有好的建议可向我们贡献代码提交适配请求,建议在 Android 11 及以上版本中使用效果最佳。"
confirmButton(text = "我知道了") { modulePrefs.put(DataConst.IGNORED_ANDROID_VERSION_TO_LOW, value = true) }
noCancelable()
}
/** 推广、恰饭 */
YukiPromoteTool.promote(context = this)
} }
else -> else ->
showDialog { showDialog {
@@ -138,10 +149,13 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
var statusBarIconCount = modulePrefs.get(DataConst.HOOK_STATUS_ICON_COUNT) var statusBarIconCount = modulePrefs.get(DataConst.HOOK_STATUS_ICON_COUNT)
var notifyIconAutoSyncTime = modulePrefs.get(DataConst.NOTIFY_ICON_FIX_AUTO_TIME) var notifyIconAutoSyncTime = modulePrefs.get(DataConst.NOTIFY_ICON_FIX_AUTO_TIME)
binding.colorIconHookItem.isVisible = modulePrefs.get(DataConst.ENABLE_MODULE) binding.colorIconHookItem.isVisible = modulePrefs.get(DataConst.ENABLE_MODULE)
binding.statusIconCountItem.isVisible = modulePrefs.get(DataConst.ENABLE_MODULE) binding.statusIconCountItem.isVisible = modulePrefs.get(DataConst.ENABLE_MODULE) && isLowerAndroidR.not()
binding.notifyIconConfigItem.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.notifyIconFixButton.isVisible = modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX)
binding.notifyIconFixNotifyItem.isVisible = modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX) binding.notifyIconCustomCornerItem.isVisible = modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX) &&
modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FORCE_APP_ICON).not() && isLowerAndroidR.not()
binding.notifyIconForceAppIconItem.isVisible = modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX)
binding.notifyIconFixNotifyItem.isVisible = modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX) && isLowerAndroidR.not()
binding.notifyIconAutoSyncItem.isVisible = modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX) binding.notifyIconAutoSyncItem.isVisible = modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX)
binding.statusIconCountSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_HOOK_STATUS_ICON_COUNT) binding.statusIconCountSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_HOOK_STATUS_ICON_COUNT)
binding.statusIconCountChildItem.isVisible = modulePrefs.get(DataConst.ENABLE_HOOK_STATUS_ICON_COUNT) binding.statusIconCountChildItem.isVisible = modulePrefs.get(DataConst.ENABLE_HOOK_STATUS_ICON_COUNT)
@@ -151,8 +165,11 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
binding.hideIconInLauncherSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_HIDE_ICON) binding.hideIconInLauncherSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_HIDE_ICON)
binding.colorIconCompatSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_COLOR_ICON_COMPAT) binding.colorIconCompatSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_COLOR_ICON_COMPAT)
binding.notifyIconFixSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX) binding.notifyIconFixSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX)
binding.notifyIconForceAppIconSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FORCE_APP_ICON)
binding.notifyIconFixNotifySwitch.isChecked = modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX_NOTIFY) binding.notifyIconFixNotifySwitch.isChecked = modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX_NOTIFY)
binding.notifyIconAutoSyncSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX_AUTO) binding.notifyIconAutoSyncSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX_AUTO)
binding.notifyIconCustomCornerSeekbar.progress = modulePrefs.get(DataConst.NOTIFY_ICON_CORNER)
binding.notifyIconCustomCornerText.text = "${modulePrefs.get(DataConst.NOTIFY_ICON_CORNER)} dp"
binding.statusIconCountText.text = statusBarIconCount.toString() binding.statusIconCountText.text = statusBarIconCount.toString()
binding.notifyIconAutoSyncText.text = notifyIconAutoSyncTime binding.notifyIconAutoSyncText.text = notifyIconAutoSyncTime
binding.moduleEnableSwitch.setOnCheckedChangeListener { btn, b -> binding.moduleEnableSwitch.setOnCheckedChangeListener { btn, b ->
@@ -160,7 +177,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
modulePrefs.put(DataConst.ENABLE_MODULE, b) modulePrefs.put(DataConst.ENABLE_MODULE, b)
binding.moduleEnableLogSwitch.isVisible = b binding.moduleEnableLogSwitch.isVisible = b
binding.colorIconHookItem.isVisible = b binding.colorIconHookItem.isVisible = b
binding.statusIconCountItem.isVisible = b binding.statusIconCountItem.isVisible = b && isLowerAndroidR.not()
binding.notifyIconConfigItem.isVisible = b binding.notifyIconConfigItem.isVisible = b
SystemUITool.showNeedRestartSnake(context = this) SystemUITool.showNeedRestartSnake(context = this)
} }
@@ -193,10 +210,30 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
if (btn.isPressed.not()) return@setOnCheckedChangeListener if (btn.isPressed.not()) return@setOnCheckedChangeListener
modulePrefs.put(DataConst.ENABLE_NOTIFY_ICON_FIX, b) modulePrefs.put(DataConst.ENABLE_NOTIFY_ICON_FIX, b)
binding.notifyIconFixButton.isVisible = b binding.notifyIconFixButton.isVisible = b
binding.notifyIconFixNotifyItem.isVisible = b binding.notifyIconCustomCornerItem.isVisible = b &&
modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FORCE_APP_ICON).not() && isLowerAndroidR.not()
binding.notifyIconForceAppIconItem.isVisible = b
binding.notifyIconFixNotifyItem.isVisible = b && isLowerAndroidR.not()
binding.notifyIconAutoSyncItem.isVisible = b binding.notifyIconAutoSyncItem.isVisible = b
SystemUITool.refreshSystemUI(context = this) SystemUITool.refreshSystemUI(context = this)
} }
binding.notifyIconForceAppIconSwitch.setOnCheckedChangeListener { btn, b ->
if (btn.isPressed.not()) return@setOnCheckedChangeListener
fun saveState() {
binding.notifyIconCustomCornerItem.isVisible = b.not() && isLowerAndroidR.not()
modulePrefs.put(DataConst.ENABLE_NOTIFY_ICON_FORCE_APP_ICON, b)
SystemUITool.refreshSystemUI(context = this)
}
if (b) showDialog {
title = "破坏性功能警告"
msg = "开启这个功能后,任何通知栏中的通知图标都会被强制替换为当前推送通知的 APP 的图标," +
"某些系统级别的 APP 通知图标可能会显示异常或发生图标丢失。\n\n" +
"此功能仅面向一些追求图标美观度的用户,我们不推荐开启这个功能,且发生任何 BUG 都不会去修复,仍然继续开启吗?"
confirmButton { saveState() }
cancelButton { btn.isChecked = btn.isChecked.not() }
noCancelable()
} else saveState()
}
binding.notifyIconFixNotifySwitch.setOnCheckedChangeListener { btn, b -> binding.notifyIconFixNotifySwitch.setOnCheckedChangeListener { btn, b ->
if (btn.isPressed.not()) return@setOnCheckedChangeListener if (btn.isPressed.not()) return@setOnCheckedChangeListener
modulePrefs.put(DataConst.ENABLE_NOTIFY_ICON_FIX_NOTIFY, b) modulePrefs.put(DataConst.ENABLE_NOTIFY_ICON_FIX_NOTIFY, b)
@@ -208,15 +245,27 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
binding.notifyIconAutoSyncChildItem.isVisible = b binding.notifyIconAutoSyncChildItem.isVisible = b
SystemUITool.refreshSystemUI(context = this, isRefreshCacheOnly = true) SystemUITool.refreshSystemUI(context = this, isRefreshCacheOnly = true)
} }
binding.notifyIconCustomCornerSeekbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
binding.notifyIconCustomCornerText.text = "$progress dp"
}
override fun onStopTrackingTouch(seekBar: SeekBar) {
modulePrefs.put(DataConst.NOTIFY_ICON_CORNER, seekBar.progress)
SystemUITool.refreshSystemUI(context = this@MainActivity)
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
})
/** 通知图标优化名单按钮点击事件 */ /** 通知图标优化名单按钮点击事件 */
binding.notifyIconFixButton.setOnClickListener { navigate<ConfigureActivity>() } binding.notifyIconFixButton.setOnClickListener { navigate<ConfigureActivity>() }
/** 设置警告 */ /** 设置警告 */
binding.warnSCountDisTip.isGone = miuiVersionCode > 12.5 binding.warnSCountDisTip.isGone = miuiVersionCode > 12.5
/** 修改状态栏通知图标个数按钮点击事件 */ /** 修改状态栏通知图标个数按钮点击事件 */
binding.statusIconCountButton.setOnClickListener { binding.statusIconCountButton.setOnClickListener {
showDialog { showDialog<DiaStatusIconCountBinding> {
title = "设置最多显示的图标个数" title = "设置最多显示的图标个数"
val editText = bind<DiaStatusIconCountBinding>().diaStatusIconCountInputEdit.apply { binding.iconCountEdit.apply {
requestFocus() requestFocus()
invalidate() invalidate()
setText(statusBarIconCount.toString()) setText(statusBarIconCount.toString())
@@ -224,12 +273,12 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
} }
confirmButton { confirmButton {
when { when {
(runCatching { editText.text.toString().toInt() }.getOrNull() ?: -1) (runCatching { binding.iconCountEdit.text.toString().toInt() }.getOrNull() ?: -1)
!in 0..100 -> snake(msg = "请输入有效数值") !in 0..100 -> snake(msg = "请输入有效数值")
editText.text.toString().isNotBlank() -> runCatching { binding.iconCountEdit.text.toString().isNotBlank() -> runCatching {
statusBarIconCount = editText.text.toString().trim().toInt() statusBarIconCount = binding.iconCountEdit.text.toString().trim().toInt()
modulePrefs.put(DataConst.HOOK_STATUS_ICON_COUNT, statusBarIconCount) modulePrefs.put(DataConst.HOOK_STATUS_ICON_COUNT, statusBarIconCount)
binding.statusIconCountText.text = statusBarIconCount.toString() this@MainActivity.binding.statusIconCountText.text = statusBarIconCount.toString()
SystemUITool.showNeedRestartSnake(context) SystemUITool.showNeedRestartSnake(context)
}.onFailure { snake(msg = "数值格式无效") } }.onFailure { snake(msg = "数值格式无效") }
else -> snake(msg = "请输入有效数值") else -> snake(msg = "请输入有效数值")
@@ -251,7 +300,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
"模块无需保持在后台运行,到达同步时间后会自动启动,如果到达时间后模块正在运行则会自动取消本次计划任务。" "模块无需保持在后台运行,到达同步时间后会自动启动,如果到达时间后模块正在运行则会自动取消本次计划任务。"
confirmButton(text = "保存设置") { confirmButton(text = "保存设置") {
notifyIconAutoSyncTime = it notifyIconAutoSyncTime = it
binding.notifyIconAutoSyncText.text = it this@MainActivity.binding.notifyIconAutoSyncText.text = it
modulePrefs.put(DataConst.NOTIFY_ICON_FIX_AUTO_TIME, it) modulePrefs.put(DataConst.NOTIFY_ICON_FIX_AUTO_TIME, it)
SystemUITool.refreshSystemUI(context, isRefreshCacheOnly = true) SystemUITool.refreshSystemUI(context, isRefreshCacheOnly = true)
} }
@@ -274,36 +323,36 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
private fun refreshModuleStatus() { private fun refreshModuleStatus() {
binding.mainLinStatus.setBackgroundResource( binding.mainLinStatus.setBackgroundResource(
when { when {
isXposedModuleActive && (isModuleRegular.not() || isModuleValied.not()) -> R.drawable.bg_yellow_round YukiHookAPI.Status.isXposedModuleActive && (isModuleRegular.not() || isModuleValied.not()) -> R.drawable.bg_yellow_round
isXposedModuleActive -> R.drawable.bg_green_round YukiHookAPI.Status.isXposedModuleActive -> R.drawable.bg_green_round
else -> R.drawable.bg_dark_round else -> R.drawable.bg_dark_round
} }
) )
binding.mainImgStatus.setImageResource( binding.mainImgStatus.setImageResource(
when { when {
isXposedModuleActive -> R.mipmap.ic_success YukiHookAPI.Status.isXposedModuleActive -> R.mipmap.ic_success
else -> R.mipmap.ic_warn else -> R.mipmap.ic_warn
} }
) )
binding.mainTextStatus.text = binding.mainTextStatus.text =
when { when {
isXposedModuleActive && isModuleRegular.not() && modulePrefs.get(DataConst.ENABLE_MODULE).not() -> "模块已停用" YukiHookAPI.Status.isXposedModuleActive && isModuleRegular.not() && modulePrefs.get(DataConst.ENABLE_MODULE).not() -> "模块已停用"
isXposedModuleActive && isModuleRegular.not() -> "模块已激活,请重启系统界面" YukiHookAPI.Status.isXposedModuleActive && isModuleRegular.not() -> "模块已激活,请重启系统界面"
isXposedModuleActive && isModuleValied.not() -> "模块已更新,请重启系统界面" YukiHookAPI.Status.isXposedModuleActive && isModuleValied.not() -> "模块已更新,请重启系统界面"
isXposedModuleActive -> "模块已激活" YukiHookAPI.Status.isXposedModuleActive -> "模块已激活"
else -> "模块未激活" else -> "模块未激活"
} }
binding.mainTextApiWay.isVisible = isXposedModuleActive binding.mainTextApiWay.isVisible = YukiHookAPI.Status.isXposedModuleActive
binding.mainTextApiWay.text = "Activated by ${YukiHookModuleStatus.executorName} API ${YukiHookModuleStatus.executorVersion}" binding.mainTextApiWay.text = "Activated by ${YukiHookAPI.Status.executorName} API ${YukiHookAPI.Status.executorVersion}"
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
/** 刷新模块状态 */ /** 刷新模块状态 */
refreshModuleStatus() refreshModuleStatus()
/** 发送广播检查模块激活状态 */ /** 检查模块激活状态 */
SystemUITool.checkingActivated(context = this) { isRegular, isValied -> SystemUITool.checkingActivated(context = this) { isValied ->
isModuleRegular = isRegular isModuleRegular = true
isModuleValied = isValied isModuleValied = isValied
refreshModuleStatus() refreshModuleStatus()
} }

View File

@@ -28,11 +28,11 @@ import android.app.Activity
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.WindowManager import android.view.WindowManager
import com.fankes.miui.notify.application.MNNApplication.Companion.appContext
import com.fankes.miui.notify.ui.activity.base.BaseActivity import com.fankes.miui.notify.ui.activity.base.BaseActivity
import com.fankes.miui.notify.utils.factory.delayedRun import com.fankes.miui.notify.utils.factory.delayedRun
import com.fankes.miui.notify.utils.tool.IconRuleManagerTool import com.fankes.miui.notify.utils.tool.IconRuleManagerTool
import com.fankes.miui.notify.utils.tool.SystemUITool import com.fankes.miui.notify.utils.tool.SystemUITool
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication.Companion.appContext
class NotifyIconRuleUpdateActivity : Activity() { class NotifyIconRuleUpdateActivity : Activity() {
@@ -48,8 +48,6 @@ class NotifyIconRuleUpdateActivity : Activity() {
finish() finish()
return return
} }
/** 注册 */
SystemUITool.register(context = this)
/** 拉取云端数据 */ /** 拉取云端数据 */
IconRuleManagerTool.sync(appContext) { IconRuleManagerTool.sync(appContext) {
/** 刷新系统界面 */ /** 刷新系统界面 */
@@ -60,10 +58,4 @@ class NotifyIconRuleUpdateActivity : Activity() {
/** 切换到后台 */ /** 切换到后台 */
moveTaskToBack(true) moveTaskToBack(true)
} }
override fun onDestroy() {
super.onDestroy()
/** 解除注册 */
SystemUITool.unregister(context = this)
}
} }

View File

@@ -26,11 +26,11 @@ package com.fankes.miui.notify.ui.activity.base
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.WindowCompat
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.fankes.miui.notify.R import com.fankes.miui.notify.R
import com.fankes.miui.notify.utils.factory.isNotSystemInDarkMode import com.fankes.miui.notify.utils.factory.isNotSystemInDarkMode
import com.fankes.miui.notify.utils.tool.SystemUITool
import com.gyf.immersionbar.ktx.immersionBar
import com.highcapable.yukihookapi.hook.factory.method import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
import java.lang.reflect.ParameterizedType import java.lang.reflect.ParameterizedType
@@ -61,26 +61,19 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
/** 隐藏系统的标题栏 */ /** 隐藏系统的标题栏 */
supportActionBar?.hide() supportActionBar?.hide()
/** 初始化沉浸状态栏 */ /** 初始化沉浸状态栏 */
immersionBar { WindowCompat.getInsetsController(window, window.decorView).apply {
statusBarColor(R.color.colorThemeBackground) isAppearanceLightStatusBars = isNotSystemInDarkMode
autoDarkModeEnable(true) isAppearanceLightNavigationBars = isNotSystemInDarkMode
statusBarDarkFont(isNotSystemInDarkMode) }
navigationBarColor(R.color.colorThemeBackground) ResourcesCompat.getColor(resources, R.color.colorThemeBackground, null).also {
navigationBarDarkIcon(isNotSystemInDarkMode) window?.statusBarColor = it
fitsSystemWindows(true) window?.navigationBarColor = it
window?.navigationBarDividerColor = it
} }
/** 注册 */
SystemUITool.register(context = this)
/** 装载子类 */ /** 装载子类 */
onCreate() onCreate()
} }
/** 回调 [onCreate] 方法 */ /** 回调 [onCreate] 方法 */
abstract fun onCreate() abstract fun onCreate()
override fun onDestroy() {
super.onDestroy()
/** 解除注册 */
SystemUITool.unregister(context = this)
}
} }

View File

@@ -20,7 +20,7 @@
* *
* This file is Created by fankes on 2022/1/8. * This file is Created by fankes on 2022/1/8.
*/ */
@file:Suppress("DEPRECATION", "CanvasSize") @file:Suppress("DEPRECATION", "CanvasSize", "OVERRIDE_DEPRECATION")
package com.fankes.miui.notify.utils.drawable.drawabletoolbox package com.fankes.miui.notify.utils.drawable.drawabletoolbox

View File

@@ -0,0 +1,87 @@
/*
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/MIUINativeNotifyIcon
*
* 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/6/3.
*/
package com.fankes.miui.notify.utils.factory
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.ListView
import androidx.viewbinding.ViewBinding
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
/**
* 绑定 [BaseAdapter] 到 [ListView]
* @param initiate 方法体
* @return [BaseAdapter]
*/
inline fun ListView.bindAdapter(initiate: BaseAdapterCreater.() -> Unit) =
BaseAdapterCreater(context).apply(initiate).baseAdapter?.apply { adapter = this } ?: error("BaseAdapter not binded")
/**
* [BaseAdapter] 创建类
* @param context 实例
*/
class BaseAdapterCreater(val context: Context) {
/** 当前 [List] 回调 */
var listDataCallback: (() -> List<*>)? = null
/** 当前 [BaseAdapter] */
var baseAdapter: BaseAdapter? = null
/**
* 绑定 [List] 到 [ListView]
* @param result 回调数据
*/
fun onBindDatas(result: (() -> List<*>)) {
listDataCallback = result
}
/**
* 绑定 [BaseAdapter] 到 [ListView]
* @param bindViews 回调 - ([VB] 每项,[Int] 下标)
*/
inline fun <reified VB : ViewBinding> onBindViews(crossinline bindViews: (binding: VB, position: Int) -> Unit) {
baseAdapter = object : BaseAdapter() {
override fun getCount() = listDataCallback?.let { it() }?.size ?: 0
override fun getItem(position: Int) = listDataCallback?.let { it() }?.get(position)
override fun getItemId(position: Int) = position.toLong()
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
var holderView = convertView
val holder: VB
if (convertView == null) {
holder = VB::class.java.method {
name = "inflate"
param(LayoutInflaterClass)
}.get().invoke<VB>(LayoutInflater.from(context)) ?: error("ViewHolder binding failed")
holderView = holder.root.apply { tag = holder }
} else holder = convertView.tag as VB
bindViews(holder, position)
return holderView ?: error("ViewHolder binding failed")
}
}
}
}

View File

@@ -39,40 +39,57 @@ import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.highcapable.yukihookapi.annotation.DoNotUseField import com.highcapable.yukihookapi.annotation.CauseProblemsApi
import com.highcapable.yukihookapi.hook.factory.method import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass 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 timeSet 当前时间 - 不写将使用当前时间格式HH:mm
* @param it 回调 - 小时与分钟 HH:mm * @param result 回调 - 小时与分钟 HH:mm
*/ */
fun Context.showTimePicker(timeSet: String = "", it: (String) -> Unit) = fun Context.showTimePicker(timeSet: String = "", result: (String) -> Unit) =
TimePickerDialog(this, { _, h, m -> it("${h.autoZero}:${m.autoZero}") }, timeSet.hour, timeSet.minute, true).show() TimePickerDialog(this, { _, h, m -> result("${h.autoZero}:${m.autoZero}") }, timeSet.hour, timeSet.minute, true).show()
/**
* 构造 [VB] 自定义 View 对话框
* @param initiate 对话框方法体
*/
@JvmName(name = "showDialog-VB")
inline fun <reified VB : ViewBinding> Context.showDialog(initiate: DialogBuilder<VB>.() -> Unit) =
DialogBuilder<VB>(context = this, VB::class.java).apply(initiate).show()
/**
* 构造对话框
* @param initiate 对话框方法体
*/
inline fun Context.showDialog(initiate: DialogBuilder<*>.() -> Unit) = DialogBuilder<ViewBinding>(context = this).apply(initiate).show()
/** /**
* 对话框构造器 * 对话框构造器
* @param context 实例 * @param context 实例
* @param isUseBlackTheme 是否使用深色主题 - 对 AndroidX 风格无效 * @param bindingClass [ViewBinding] 的 [Class] 实例 or null
*/ */
class DialogBuilder(val context: Context, private val isUseBlackTheme: Boolean) { class DialogBuilder<VB : ViewBinding>(val context: Context, private val bindingClass: Class<*>? = null) {
private var instanceAndroidX: androidx.appcompat.app.AlertDialog.Builder? = null // 实例对象 private var instanceAndroidX: androidx.appcompat.app.AlertDialog.Builder? = null // 实例对象
private var instanceAndroid: android.app.AlertDialog.Builder? = null // 实例对象 private var instanceAndroid: android.app.AlertDialog.Builder? = null // 实例对象
private var dialogInstance: Dialog? = null // 对话框实例 private var dialogInstance: Dialog? = null // 对话框实例
private var customLayoutView: View? = null // 自定义布局
@DoNotUseField /**
var customLayoutView: View? = null // 自定义布局 * 获取 [DialogBuilder] 绑定布局对象
* @return [VB]
*/
val binding by lazy {
bindingClass?.method {
name = "inflate"
param(LayoutInflaterClass)
}?.get()?.invoke<VB>(LayoutInflater.from(context))?.apply {
customLayoutView = root
} ?: error("This dialog maybe not a custom view dialog")
}
/** /**
* 是否需要使用 AndroidX 风格对话框 * 是否需要使用 AndroidX 风格对话框
@@ -83,12 +100,7 @@ class DialogBuilder(val context: Context, private val isUseBlackTheme: Boolean)
init { init {
if (isUsingAndroidX) if (isUsingAndroidX)
runInSafe { instanceAndroidX = MaterialAlertDialogBuilder(context) } runInSafe { instanceAndroidX = MaterialAlertDialogBuilder(context) }
else runInSafe { else runInSafe { instanceAndroid = android.app.AlertDialog.Builder(context, android.R.style.Theme_Material_Light_Dialog) }
instanceAndroid = android.app.AlertDialog.Builder(
context,
if (isUseBlackTheme) android.R.style.Theme_Material_Dialog else android.R.style.Theme_Material_Light_Dialog
)
}
} }
/** 设置对话框不可关闭 */ /** 设置对话框不可关闭 */
@@ -135,56 +147,47 @@ class DialogBuilder(val context: Context, private val isUseBlackTheme: Boolean)
else customLayoutView?.findViewWithTag<TextView>("progressContent")?.text = value 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 text 按钮文本内容
* @param it 点击事件 * @param callback 点击事件
*/ */
fun confirmButton(text: String = "确定", it: () -> Unit = {}) { fun confirmButton(text: String = "确定", callback: () -> Unit = {}) {
if (isUsingAndroidX) if (isUsingAndroidX)
runInSafe { instanceAndroidX?.setPositiveButton(text) { _, _ -> it() } } runInSafe { instanceAndroidX?.setPositiveButton(text) { _, _ -> callback() } }
else runInSafe { instanceAndroid?.setPositiveButton(text) { _, _ -> it() } } else runInSafe { instanceAndroid?.setPositiveButton(text) { _, _ -> callback() } }
} }
/** /**
* 设置对话框取消按钮 * 设置对话框取消按钮
* @param text 按钮文本内容 * @param text 按钮文本内容
* @param it 点击事件 * @param callback 点击事件
*/ */
fun cancelButton(text: String = "取消", it: () -> Unit = {}) { fun cancelButton(text: String = "取消", callback: () -> Unit = {}) {
if (isUsingAndroidX) if (isUsingAndroidX)
runInSafe { instanceAndroidX?.setNegativeButton(text) { _, _ -> it() } } runInSafe { instanceAndroidX?.setNegativeButton(text) { _, _ -> callback() } }
else runInSafe { instanceAndroid?.setNegativeButton(text) { _, _ -> it() } } else runInSafe { instanceAndroid?.setNegativeButton(text) { _, _ -> callback() } }
} }
/** /**
* 设置对话框第三个按钮 * 设置对话框第三个按钮
* @param text 按钮文本内容 * @param text 按钮文本内容
* @param it 点击事件 * @param callback 点击事件
*/ */
fun neutralButton(text: String = "更多", it: () -> Unit = {}) { fun neutralButton(text: String = "更多", callback: () -> Unit = {}) {
if (isUsingAndroidX) if (isUsingAndroidX)
runInSafe { instanceAndroidX?.setNeutralButton(text) { _, _ -> it() } } runInSafe { instanceAndroidX?.setNeutralButton(text) { _, _ -> callback() } }
else runInSafe { instanceAndroid?.setNeutralButton(text) { _, _ -> it() } } else runInSafe { instanceAndroid?.setNeutralButton(text) { _, _ -> callback() } }
} }
/** 取消对话框 */ /** 取消对话框 */
fun cancel() = dialogInstance?.cancel() fun cancel() = dialogInstance?.cancel()
/** 显示对话框 */ /** 显示对话框 */
internal fun show() = @CauseProblemsApi
fun show() {
/** 若当前自定义 View 的对话框没有调用 [binding] 将会对其手动调用一次以确保显示布局 */
if (bindingClass != null) binding
if (isUsingAndroidX) runInSafe { if (isUsingAndroidX) runInSafe {
instanceAndroidX?.create()?.apply { instanceAndroidX?.create()?.apply {
customLayoutView?.let { setView(it) } customLayoutView?.let { setView(it) }
@@ -196,8 +199,7 @@ class DialogBuilder(val context: Context, private val isUseBlackTheme: Boolean)
window?.setBackgroundDrawable( window?.setBackgroundDrawable(
GradientDrawable( GradientDrawable(
GradientDrawable.Orientation.TOP_BOTTOM, GradientDrawable.Orientation.TOP_BOTTOM,
if (isUseBlackTheme) intArrayOf(0xFF2D2D2D.toInt(), 0xFF2D2D2D.toInt()) intArrayOf(Color.WHITE, Color.WHITE)
else intArrayOf(Color.WHITE, Color.WHITE)
).apply { ).apply {
shape = GradientDrawable.RECTANGLE shape = GradientDrawable.RECTANGLE
gradientType = GradientDrawable.LINEAR_GRADIENT gradientType = GradientDrawable.LINEAR_GRADIENT
@@ -206,4 +208,5 @@ class DialogBuilder(val context: Context, private val isUseBlackTheme: Boolean)
dialogInstance = this dialogInstance = this
}?.show() }?.show()
} }
}
} }

View File

@@ -32,6 +32,7 @@ import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Configuration import android.content.res.Configuration
@@ -46,17 +47,18 @@ import android.util.Base64
import android.widget.Toast import android.widget.Toast
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import com.fankes.miui.notify.application.MNNApplication.Companion.appContext
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.highcapable.yukihookapi.hook.factory.classOf import com.highcapable.yukihookapi.hook.factory.classOf
import com.highcapable.yukihookapi.hook.factory.hasClass import com.highcapable.yukihookapi.hook.factory.hasClass
import com.highcapable.yukihookapi.hook.factory.method import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.type.java.StringType import com.highcapable.yukihookapi.hook.type.java.StringType
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication.Companion.appContext
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
/** /**
* 系统深色模式是否开启 * 系统深色模式是否开启
* @return [Boolean] 是否开启 * @return [Boolean] 是否开启
@@ -93,6 +95,12 @@ inline val isUpperOfAndroidS get() = Build.VERSION.SDK_INT > Build.VERSION_CODES
*/ */
inline val isLowerAndroidP get() = Build.VERSION.SDK_INT < Build.VERSION_CODES.P inline val isLowerAndroidP get() = Build.VERSION.SDK_INT < Build.VERSION_CODES.P
/**
* 系统版本是否低于 Android 11
* @return [Boolean] 是否符合条件
*/
inline val isLowerAndroidR get() = Build.VERSION.SDK_INT < Build.VERSION_CODES.R
/** /**
* 当前设备是否是 MIUI 定制 Android 系统 * 当前设备是否是 MIUI 定制 Android 系统
* @return [Boolean] 是否符合条件 * @return [Boolean] 是否符合条件
@@ -237,6 +245,14 @@ fun Context.findAppName(name: String) =
fun Context.findAppIcon(name: String) = fun Context.findAppIcon(name: String) =
safeOfNull { packageManager?.getPackageInfo(name, 0)?.applicationInfo?.loadIcon(packageManager) } 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] * @return [String]
@@ -402,12 +418,19 @@ fun findPropString(key: String, default: String = "") = safeOf(default) {
} }
/** /**
* 执行命令 - su * 是否有 Root 权限
* @return [Boolean]
*/
val isRootAccess get() = safeOfFalse { Shell.rootAccess() }
/**
* 执行命令
* @param cmd 命令 * @param cmd 命令
* @param isSu 是否使用 Root 权限执行 - 默认:是
* @return [String] 执行结果 * @return [String] 执行结果
*/ */
fun execShellSu(cmd: String) = safeOfNothing { fun execShell(cmd: String, isSu: Boolean = true) = safeOfNothing {
Shell.su(cmd).exec().out.let { (if (isSu) Shell.su(cmd) else Shell.sh(cmd)).exec().out.let {
if (it.isNotEmpty()) it[0].trim() else "" if (it.isNotEmpty()) it[0].trim() else ""
} }
} }
@@ -423,28 +446,24 @@ fun toast(msg: String) = Toast.makeText(appContext, msg, Toast.LENGTH_SHORT).sho
* *
* [T] 为指定的 [Activity] * [T] 为指定的 [Activity]
*/ */
inline fun <reified T : Activity> Context.navigate() = runInSafe { startActivity(Intent(this, T::class.java)) } inline fun <reified T : Activity> Context.navigate() = runInSafe {
startActivity(Intent(if (this is Service) applicationContext else this, T::class.java).apply {
/** if (this@navigate !is Activity) flags = Intent.FLAG_ACTIVITY_NEW_TASK
* 跳转到指定页面 })
* }
* [T] 为指定的 [Activity]
*/
inline fun <reified T : Activity> Service.navigate() =
runInSafe { startActivity(Intent(this, T::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }) }
/** /**
* 弹出 [Snackbar] * 弹出 [Snackbar]
* @param msg 提示内容 * @param msg 提示内容
* @param actionText 按钮文本 - 不写默认取消按钮 * @param actionText 按钮文本 - 不写默认取消按钮
* @param it 按钮事件回调 * @param callback 按钮事件回调
*/ */
fun Context.snake(msg: String, actionText: String = "", it: () -> Unit = {}) = fun Context.snake(msg: String, actionText: String = "", callback: () -> Unit = {}) =
Snackbar.make((this as Activity).findViewById(android.R.id.content), msg, Snackbar.LENGTH_LONG) Snackbar.make((this as Activity).findViewById(android.R.id.content), msg, Snackbar.LENGTH_LONG)
.apply { .apply {
if (actionText.isBlank()) return@apply if (actionText.isBlank()) return@apply
setActionTextColor(if (isSystemInDarkMode) Color.BLACK else Color.WHITE) setActionTextColor(if (isSystemInDarkMode) Color.BLACK else Color.WHITE)
setAction(actionText) { it() } setAction(actionText) { callback() }
}.show() }.show()
/** /**

View File

@@ -24,11 +24,15 @@ package com.fankes.miui.notify.utils.tool
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.icu.text.SimpleDateFormat
import android.icu.util.Calendar
import android.icu.util.TimeZone
import com.fankes.miui.notify.utils.factory.* import com.fankes.miui.notify.utils.factory.*
import okhttp3.* import okhttp3.*
import org.json.JSONObject import org.json.JSONObject
import java.io.IOException import java.io.IOException
import java.io.Serializable import java.io.Serializable
import java.util.*
/** /**
* 获取 Github Release 最新版本工具类 * 获取 Github Release 最新版本工具类
@@ -45,9 +49,9 @@ object GithubReleaseTool {
* 获取最新版本信息 * 获取最新版本信息
* @param context 实例 * @param context 实例
* @param version 当前版本 * @param version 当前版本
* @param it 成功后回调 - ([String] 最新版本,[Function] 更新对话框方法体) * @param result 成功后回调 - ([String] 最新版本,[Function] 更新对话框方法体)
*/ */
fun checkingForUpdate(context: Context, version: String, it: (String, () -> Unit) -> Unit) = checkingInternetConnect(context) { fun checkingForUpdate(context: Context, version: String, result: (String, () -> Unit) -> Unit) = checkingInternetConnect(context) {
OkHttpClient().newBuilder().build().newCall( OkHttpClient().newBuilder().build().newCall(
Request.Builder() Request.Builder()
.url("https://api.github.com/repos/$REPO_AUTHOR/$REPO_NAME/releases/latest") .url("https://api.github.com/repos/$REPO_AUTHOR/$REPO_NAME/releases/latest")
@@ -57,12 +61,12 @@ object GithubReleaseTool {
override fun onFailure(call: Call, e: IOException) {} override fun onFailure(call: Call, e: IOException) {}
override fun onResponse(call: Call, response: Response) = runInSafe { override fun onResponse(call: Call, response: Response) = runInSafe {
JSONObject(response.body?.string() ?: "").apply { JSONObject(response.body.string()).apply {
GithubReleaseBean( GithubReleaseBean(
name = getString("name"), name = getString("name"),
htmlUrl = getString("html_url"), htmlUrl = getString("html_url"),
content = getString("body"), content = getString("body"),
date = getString("published_at").replace(oldValue = "T", newValue = " ").replace(oldValue = "Z", newValue = "") date = getString("published_at").localTime()
).apply { ).apply {
fun showUpdate() = context.showDialog { fun showUpdate() = context.showDialog {
title = "最新版本 $name" title = "最新版本 $name"
@@ -73,7 +77,7 @@ object GithubReleaseTool {
} }
if (name != version) (context as? Activity?)?.runOnUiThread { if (name != version) (context as? Activity?)?.runOnUiThread {
showUpdate() showUpdate()
it(name) { showUpdate() } result(name) { showUpdate() }
} }
} }
} }
@@ -84,9 +88,9 @@ object GithubReleaseTool {
/** /**
* 检查网络连接情况 * 检查网络连接情况
* @param context 实例 * @param context 实例
* @param it 已连接回调 * @param callback 已连接回调
*/ */
private fun checkingInternetConnect(context: Context, it: () -> Unit) = runInSafe { private fun checkingInternetConnect(context: Context, callback: () -> Unit) = runInSafe {
if (isNetWorkSuccess) if (isNetWorkSuccess)
OkHttpClient().newBuilder().build().newCall( OkHttpClient().newBuilder().build().newCall(
Request.Builder() Request.Builder()
@@ -107,11 +111,23 @@ object GithubReleaseTool {
} }
override fun onResponse(call: Call, response: Response) = runInSafe { override fun onResponse(call: Call, response: Response) = runInSafe {
(context as? Activity?)?.runOnUiThread { runInSafe { it() } } (context as? Activity?)?.runOnUiThread { runInSafe { callback() } }
} }
}) })
} }
/**
* 格式化时间为本地时区
* @return [String] 本地时区时间
*/
private fun String.localTime() = replace(oldValue = "T", newValue = " ").replace(oldValue = "Z", newValue = "").let {
runCatching {
val local = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ROOT).apply { timeZone = Calendar.getInstance().timeZone }
val current = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ROOT).apply { timeZone = TimeZone.getTimeZone("GMT") }
local.format(current.parse(it))
}.getOrNull() ?: it
}
/** /**
* Github Release bean * Github Release bean
* @param name 版本名称 * @param name 版本名称

View File

@@ -33,7 +33,7 @@ import android.graphics.Bitmap
import android.graphics.drawable.Icon import android.graphics.drawable.Icon
import android.os.Build import android.os.Build
import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toBitmap
import com.fankes.miui.notify.const.Const import com.fankes.miui.notify.BuildConfig
import com.fankes.miui.notify.hook.HookEntry import com.fankes.miui.notify.hook.HookEntry
import com.fankes.miui.notify.utils.factory.* import com.fankes.miui.notify.utils.factory.*
@@ -44,6 +44,9 @@ import com.fankes.miui.notify.utils.factory.*
*/ */
object IconAdaptationTool { object IconAdaptationTool {
/** 当前模块的包名 */
private const val MODULE_PACKAGE_NAME = BuildConfig.APPLICATION_ID
/** 推送通知的渠道名称 */ /** 推送通知的渠道名称 */
private const val NOTIFY_CHANNEL = "notifyRuleSupportId" private const val NOTIFY_CHANNEL = "notifyRuleSupportId"
@@ -116,6 +119,7 @@ object IconAdaptationTool {
* @param packageName 安装的 APP 包名 * @param packageName 安装的 APP 包名
*/ */
fun pushNewAppSupportNotify(context: Context, packageName: String) { fun pushNewAppSupportNotify(context: Context, packageName: String) {
if (context.isAppDebuggable(packageName)) return
context.getSystemService(NotificationManager::class.java)?.apply { context.getSystemService(NotificationManager::class.java)?.apply {
createNotificationChannel( createNotificationChannel(
NotificationChannel( NotificationChannel(
@@ -136,8 +140,8 @@ object IconAdaptationTool {
context, packageName.hashCode(), context, packageName.hashCode(),
Intent().apply { Intent().apply {
component = ComponentName( component = ComponentName(
Const.MODULE_PACKAGE_NAME, MODULE_PACKAGE_NAME,
"${Const.MODULE_PACKAGE_NAME}.ui.activity.ConfigureActivity" "${MODULE_PACKAGE_NAME}.ui.activity.ConfigureActivity"
) )
flags = Intent.FLAG_ACTIVITY_NEW_TASK flags = Intent.FLAG_ACTIVITY_NEW_TASK
}.apply { }.apply {
@@ -176,8 +180,8 @@ object IconAdaptationTool {
context.startActivity( context.startActivity(
Intent().apply { Intent().apply {
component = ComponentName( component = ComponentName(
Const.MODULE_PACKAGE_NAME, MODULE_PACKAGE_NAME,
"${Const.MODULE_PACKAGE_NAME}.ui.activity.auto.NotifyIconRuleUpdateActivity" "${MODULE_PACKAGE_NAME}.ui.activity.auto.NotifyIconRuleUpdateActivity"
) )
flags = Intent.FLAG_ACTIVITY_NEW_TASK flags = Intent.FLAG_ACTIVITY_NEW_TASK
} }

View File

@@ -75,60 +75,52 @@ object IconRuleManagerTool {
/** /**
* 从在线地址手动同步规则 * 从在线地址手动同步规则
* @param context 实例 * @param context 实例
* @param it 成功后回调 * @param callback 成功后回调
*/ */
fun syncByHand(context: Context, it: () -> Unit) = fun syncByHand(context: Context, callback: () -> Unit) =
context.showDialog { context.showDialog<DiaSourceFromBinding> {
title = "同步列表" title = "同步列表"
var sourceType = context.modulePrefs.get(DataConst.SOURCE_SYNC_WAY) var sourceType = context.modulePrefs.get(DataConst.SOURCE_SYNC_WAY)
var customUrl = context.modulePrefs.get(DataConst.SOURCE_SYNC_WAY_CUSTOM_URL) var customUrl = context.modulePrefs.get(DataConst.SOURCE_SYNC_WAY_CUSTOM_URL)
bind<DiaSourceFromBinding>().apply { binding.sourceUrlEdit.apply {
diaSfText.apply { if (customUrl.isNotBlank()) {
if (customUrl.isNotBlank()) { setText(customUrl)
setText(customUrl) setSelection(customUrl.length)
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
} }
doOnTextChanged { text, _, _, _ -> customUrl = text.toString() }
}
binding.sourceFromTextLin.isVisible = sourceType == TYPE_SOURCE_SYNC_WAY_3
binding.sourceRadio1.isChecked = sourceType == TYPE_SOURCE_SYNC_WAY_1
binding.sourceRadio2.isChecked = sourceType == TYPE_SOURCE_SYNC_WAY_2
binding.sourceRadio3.isChecked = sourceType == TYPE_SOURCE_SYNC_WAY_3
binding.sourceRadio1.setOnClickListener {
binding.sourceFromTextLin.isVisible = false
sourceType = TYPE_SOURCE_SYNC_WAY_1
}
binding.sourceRadio2.setOnClickListener {
binding.sourceFromTextLin.isVisible = false
sourceType = TYPE_SOURCE_SYNC_WAY_2
}
binding.sourceRadio3.setOnClickListener {
binding.sourceFromTextLin.isVisible = true
sourceType = TYPE_SOURCE_SYNC_WAY_3
} }
confirmButton { confirmButton {
context.modulePrefs.put(DataConst.SOURCE_SYNC_WAY, sourceType) context.modulePrefs.put(DataConst.SOURCE_SYNC_WAY, sourceType)
context.modulePrefs.put(DataConst.SOURCE_SYNC_WAY_CUSTOM_URL, customUrl) context.modulePrefs.put(DataConst.SOURCE_SYNC_WAY_CUSTOM_URL, customUrl)
sync(context, sourceType, customUrl, it) sync(context, sourceType, customUrl, callback)
} }
cancelButton() cancelButton()
neutralButton(text = "自定义规则") { neutralButton(text = "自定义规则") {
context.showDialog { context.showDialog<DiaSourceFromStringBinding> {
title = "自定义规则" title = "自定义规则(调试)"
val editText = bind<DiaSourceFromStringBinding>().diaSfsInputEdit.apply { binding.jsonRuleEdit.apply {
requestFocus() requestFocus()
invalidate() invalidate()
} }
IconPackParams(context).also { params -> IconPackParams(context).also { params ->
confirmButton(text = "合并") { confirmButton(text = "合并") {
editText.text.toString().also { jsonString -> binding.jsonRuleEdit.text.toString().also { jsonString ->
when { when {
jsonString.isNotBlank() && params.isNotVaildJson(jsonString) -> context.snake(msg = "不是有效的 JSON 数据") jsonString.isNotBlank() && params.isNotVaildJson(jsonString) -> context.snake(msg = "不是有效的 JSON 数据")
jsonString.isNotBlank() -> { jsonString.isNotBlank() -> {
@@ -139,20 +131,20 @@ object IconRuleManagerTool {
) )
) )
notifyRefresh(context) notifyRefresh(context)
it() callback()
} }
else -> context.snake(msg = "请输入有效内容") else -> context.snake(msg = "请输入有效内容")
} }
} }
} }
cancelButton(text = "覆盖") { cancelButton(text = "覆盖") {
editText.text.toString().also { jsonString -> binding.jsonRuleEdit.text.toString().also { jsonString ->
when { when {
jsonString.isNotBlank() && params.isNotVaildJson(jsonString) -> context.snake(msg = "不是有效的 JSON 数据") jsonString.isNotBlank() && params.isNotVaildJson(jsonString) -> context.snake(msg = "不是有效的 JSON 数据")
jsonString.isNotBlank() -> { jsonString.isNotBlank() -> {
params.save(dataJson = jsonString.takeIf { params.isJsonArray(it) } ?: "[$jsonString]") params.save(dataJson = jsonString.takeIf { params.isJsonArray(it) } ?: "[$jsonString]")
notifyRefresh(context) notifyRefresh(context)
it() callback()
} }
else -> context.snake(msg = "请输入有效内容") else -> context.snake(msg = "请输入有效内容")
} }
@@ -169,23 +161,23 @@ object IconRuleManagerTool {
* @param context 实例 * @param context 实例
* @param sourceType 同步地址类型 - 默认自动获取已存储的键值 * @param sourceType 同步地址类型 - 默认自动获取已存储的键值
* @param customUrl 自定义同步地址 - 默认自动获取已存储的键值 * @param customUrl 自定义同步地址 - 默认自动获取已存储的键值
* @param it 成功后回调 * @param callback 成功后回调
*/ */
fun sync( fun sync(
context: Context, context: Context,
sourceType: Int = context.modulePrefs.get(DataConst.SOURCE_SYNC_WAY), sourceType: Int = context.modulePrefs.get(DataConst.SOURCE_SYNC_WAY),
customUrl: String = context.modulePrefs.get(DataConst.SOURCE_SYNC_WAY_CUSTOM_URL), customUrl: String = context.modulePrefs.get(DataConst.SOURCE_SYNC_WAY_CUSTOM_URL),
it: () -> Unit callback: () -> Unit
) { ) {
when (sourceType) { when (sourceType) {
TYPE_SOURCE_SYNC_WAY_1 -> TYPE_SOURCE_SYNC_WAY_1 ->
onRefreshing(context, url = "https://raw.fastgit.org/fankes/AndroidNotifyIconAdapt/main", it) onRefreshing(context, url = "https://raw.fastgit.org/fankes/AndroidNotifyIconAdapt/main", callback)
TYPE_SOURCE_SYNC_WAY_2 -> TYPE_SOURCE_SYNC_WAY_2 ->
onRefreshing(context, url = "https://raw.githubusercontent.com/fankes/AndroidNotifyIconAdapt/main", it) onRefreshing(context, url = "https://raw.githubusercontent.com/fankes/AndroidNotifyIconAdapt/main", callback)
TYPE_SOURCE_SYNC_WAY_3 -> TYPE_SOURCE_SYNC_WAY_3 ->
if (customUrl.isNotBlank()) if (customUrl.isNotBlank())
if (customUrl.startsWith("http://") || customUrl.startsWith("https://")) if (customUrl.startsWith("http://") || customUrl.startsWith("https://"))
onRefreshingCustom(context, customUrl, it) onRefreshingCustom(context, customUrl, callback)
else context.snakeOrNotify(title = "同步失败", msg = "同步地址不是一个合法的 URL") else context.snakeOrNotify(title = "同步失败", msg = "同步地址不是一个合法的 URL")
else context.snakeOrNotify(title = "同步失败", msg = "同步地址不能为空") else context.snakeOrNotify(title = "同步失败", msg = "同步地址不能为空")
else -> context.snakeOrNotify(title = "同步异常", msg = "同步类型错误") else -> context.snakeOrNotify(title = "同步异常", msg = "同步类型错误")
@@ -196,14 +188,14 @@ object IconRuleManagerTool {
* 开始更新数据 * 开始更新数据
* @param context 实例 * @param context 实例
* @param url * @param url
* @param it 成功后回调 * @param callback 成功后回调
*/ */
private fun onRefreshing(context: Context, url: String, it: () -> Unit) = checkingInternetConnect(context) { private fun onRefreshing(context: Context, url: String, callback: () -> Unit) = checkingInternetConnect(context) {
fun doParse(callback: (Boolean) -> Unit = {}) { fun doParse(result: (Boolean) -> Unit = {}) {
wait(context, url = "$url/OS/$OS_TAG/NotifyIconsSupportConfig.json") { isDone1, ctOS -> wait(context, url = "$url/OS/$OS_TAG/NotifyIconsSupportConfig.json") { isDone1, ctOS ->
callback(true) result(true)
wait(context, url = "$url/APP/NotifyIconsSupportConfig.json") { isDone2, ctAPP -> wait(context, url = "$url/APP/NotifyIconsSupportConfig.json") { isDone2, ctAPP ->
callback(false) result(false)
IconPackParams(context).also { params -> IconPackParams(context).also { params ->
when { when {
isDone1 && isDone2 -> params.splicingJsonArray(ctOS, ctAPP).also { isDone1 && isDone2 -> params.splicingJsonArray(ctOS, ctAPP).also {
@@ -216,7 +208,7 @@ object IconRuleManagerTool {
params.save(it) params.save(it)
pushNotify(context, title = "同步完成", msg = "已更新通知图标优化名单,点击查看") pushNotify(context, title = "同步完成", msg = "已更新通知图标优化名单,点击查看")
notifyRefresh(context) notifyRefresh(context)
it() callback()
} }
else -> (if (context is AppCompatActivity) context.snake(msg = "列表数据已是最新")) else -> (if (context is AppCompatActivity) context.snake(msg = "列表数据已是最新"))
} }
@@ -225,7 +217,7 @@ object IconRuleManagerTool {
context.showDialog { context.showDialog {
title = "连接失败" title = "连接失败"
msg = "连接失败,错误如下:\n${if (isDone1.not()) ctOS else ctAPP}" msg = "连接失败,错误如下:\n${if (isDone1.not()) ctOS else ctAPP}"
confirmButton(text = "再试一次") { syncByHand(context, it) } confirmButton(text = "再试一次") { syncByHand(context, callback) }
cancelButton() cancelButton()
} }
else -> pushNotify(context, title = "同步地址不可用", msg = if (isDone1.not()) ctOS else ctAPP) else -> pushNotify(context, title = "同步地址不可用", msg = if (isDone1.not()) ctOS else ctAPP)
@@ -248,12 +240,12 @@ object IconRuleManagerTool {
* 开始更新数据 * 开始更新数据
* @param context 实例 * @param context 实例
* @param url * @param url
* @param it 成功后回调 * @param callback 成功后回调
*/ */
private fun onRefreshingCustom(context: Context, url: String, it: () -> Unit) = checkingInternetConnect(context) { private fun onRefreshingCustom(context: Context, url: String, callback: () -> Unit) = checkingInternetConnect(context) {
fun doParse(callback: () -> Unit = {}) { fun doParse(result: () -> Unit = {}) {
wait(context, url) { isDone, content -> wait(context, url) { isDone, content ->
callback() result()
IconPackParams(context).also { params -> IconPackParams(context).also { params ->
when { when {
isDone -> when { isDone -> when {
@@ -265,7 +257,7 @@ object IconRuleManagerTool {
params.save(content) params.save(content)
pushNotify(context, title = "同步完成", msg = "已更新通知图标优化名单,点击查看") pushNotify(context, title = "同步完成", msg = "已更新通知图标优化名单,点击查看")
notifyRefresh(context) notifyRefresh(context)
it() callback()
} }
else -> (if (context is AppCompatActivity) context.snake(msg = "列表数据已是最新")) else -> (if (context is AppCompatActivity) context.snake(msg = "列表数据已是最新"))
} }
@@ -293,16 +285,16 @@ object IconRuleManagerTool {
/** /**
* 检查网络连接情况 * 检查网络连接情况
* @param context 实例 * @param context 实例
* @param it 已连接回调 * @param callback 已连接回调
*/ */
private fun checkingInternetConnect(context: Context, it: () -> Unit) = private fun checkingInternetConnect(context: Context, callback: () -> Unit) =
if (context is AppCompatActivity) context.showDialog { if (context is AppCompatActivity) context.showDialog {
title = "准备中" title = "准备中"
progressContent = "正在检查网络连接情况" progressContent = "正在检查网络连接情况"
noCancelable() noCancelable()
baseCheckingInternetConnect(context) { isDone -> baseCheckingInternetConnect(context) { isDone ->
cancel() cancel()
if (isDone) it() else if (isDone) callback() else
context.showDialog { context.showDialog {
title = "网络不可用" title = "网络不可用"
msg = "无法连接到互联网,请检查你当前的设备是否可以上网,且没有在手机管家中禁用本模块的联网权限。" msg = "无法连接到互联网,请检查你当前的设备是否可以上网,且没有在手机管家中禁用本模块的联网权限。"
@@ -319,24 +311,24 @@ object IconRuleManagerTool {
} }
} }
} else baseCheckingInternetConnect(context) { isDone -> } else baseCheckingInternetConnect(context) { isDone ->
if (isDone) it() else pushNotify(context, title = "网络不可用", msg = "无法连接到互联网,无法更新通知图标规则") if (isDone) callback() else pushNotify(context, title = "网络不可用", msg = "无法连接到互联网,无法更新通知图标规则")
} }
/** /**
* 检查网络连接情况 * 检查网络连接情况
* @param context 实例 * @param context 实例
* @param it 已连接回调 * @param result 已连接回调
*/ */
private fun baseCheckingInternetConnect(context: Context, it: (Boolean) -> Unit) = private fun baseCheckingInternetConnect(context: Context, result: (Boolean) -> Unit) =
wait(context, url = "https://www.baidu.com") { isDone, _ -> it(isDone) } wait(context, url = "https://www.baidu.com") { isDone, _ -> result(isDone) }
/** /**
* 发送 GET 请求内容并等待 * 发送 GET 请求内容并等待
* @param context 实例 * @param context 实例
* @param url 请求地址 * @param url 请求地址
* @param it 回调 - ([Boolean] 是否成功,[String] 成功的内容或失败消息) * @param result 回调 - ([Boolean] 是否成功,[String] 成功的内容或失败消息)
*/ */
private fun wait(context: Context, url: String, it: (Boolean, String) -> Unit) = runCatching { private fun wait(context: Context, url: String, result: (Boolean, String) -> Unit) = runCatching {
OkHttpClient().newBuilder().apply { OkHttpClient().newBuilder().apply {
SSLSocketClient.sSLSocketFactory?.let { sslSocketFactory(it, SSLSocketClient.trustManager) } SSLSocketClient.sSLSocketFactory?.let { sslSocketFactory(it, SSLSocketClient.trustManager) }
hostnameVerifier(SSLSocketClient.hostnameVerifier) hostnameVerifier(SSLSocketClient.hostnameVerifier)
@@ -347,15 +339,15 @@ object IconRuleManagerTool {
.build() .build()
).enqueue(object : Callback { ).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) { override fun onFailure(call: Call, e: IOException) {
(context as? Activity?)?.runOnUiThread { it(false, e.toString()) } ?: it(false, e.toString()) (context as? Activity?)?.runOnUiThread { result(false, e.toString()) } ?: result(false, e.toString())
} }
override fun onResponse(call: Call, response: Response) { override fun onResponse(call: Call, response: Response) {
val bodyString = response.body?.string() ?: "" val bodyString = response.body.string()
(context as? Activity?)?.runOnUiThread { it(true, bodyString) } ?: it(true, bodyString) (context as? Activity?)?.runOnUiThread { result(true, bodyString) } ?: result(true, bodyString)
} }
}) })
}.onFailure { it(false, "URL 无效") } }.onFailure { result(false, "URL 无效") }
/** /**
* 推送通知图标更新通知 * 推送通知图标更新通知

View File

@@ -22,59 +22,44 @@
*/ */
package com.fankes.miui.notify.utils.tool package com.fankes.miui.notify.utils.tool
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import com.fankes.miui.notify.hook.HookConst.SYSTEMUI_PACKAGE_NAME
import android.content.IntentFilter
import com.fankes.miui.notify.application.MNNApplication.Companion.appContext
import com.fankes.miui.notify.const.Const
import com.fankes.miui.notify.utils.factory.* import com.fankes.miui.notify.utils.factory.*
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.highcapable.yukihookapi.hook.factory.isXposedModuleActive import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.hook.factory.dataChannel
import com.highcapable.yukihookapi.hook.param.PackageParam
import com.highcapable.yukihookapi.hook.xposed.channel.data.ChannelData
/** /**
* 系统界面工具 * 系统界面工具
*/ */
object SystemUITool { object SystemUITool {
/** 宿主广播回调 */ private val CALL_HOST_REFRESH_CACHING = ChannelData("call_host_refresh_caching", false)
private var moduleHandlerCallback: ((Boolean, Boolean) -> Unit)? = null private val CALL_MODULE_REFRESH_RESULT = ChannelData("call_module_refresh_result", false)
/** 通知广播回调 */
private var remindHandlerCallback: ((Boolean, Boolean) -> Unit)? = null
/** /**
* 注册广播 * 宿主注册监听
* @param context 实例
*/ */
fun register(context: Context) { object Host {
/** 注册广播检查模块激活状态 */
context.registerReceiver(moduleHandlerReceiver, IntentFilter().apply { addAction(Const.ACTION_MODULE_HANDLER_RECEIVER) })
/** 注册广播通知系统界面改变 */
context.registerReceiver(remindHandlerReceiver, IntentFilter().apply { addAction(Const.ACTION_REMIND_HANDLER_RECEIVER) })
}
/** /**
* 取消注册广播 * 监听系统界面刷新改变
* @param context 实例 * @param param 实例
*/ * @param result 回调 - ([Boolean] 是否成功)
fun unregister(context: Context) { */
context.unregisterReceiver(moduleHandlerReceiver) fun onRefreshSystemUI(param: PackageParam, result: (Boolean) -> Boolean) {
context.unregisterReceiver(remindHandlerReceiver) param.dataChannel.with { wait(CALL_HOST_REFRESH_CACHING) { put(CALL_MODULE_REFRESH_RESULT, result(it)) } }
}
} }
/** /**
* 检查模块是否激活 * 检查模块是否激活
* @param context 实例 * @param context 实例
* @param it 成功后回调 - ([Boolean] 是否激活,[Boolean] 是否有效) * @param result 成功后回调
*/ */
fun checkingActivated(context: Context, it: (Boolean, Boolean) -> Unit) { fun checkingActivated(context: Context, result: (Boolean) -> Unit) = context.dataChannel(SYSTEMUI_PACKAGE_NAME).checkingVersionEquals(result)
moduleHandlerCallback = it
context.sendBroadcast(Intent().apply {
action = Const.ACTION_MODULE_CHECKING_RECEIVER
putExtra(Const.MODULE_VERSION_VERIFY_TAG, Const.MODULE_VERSION_VERIFY)
})
}
/** /**
* 重启系统界面 * 重启系统界面
@@ -87,9 +72,9 @@ object SystemUITool {
"部分 MIUI 内测和开发版中使用了状态栏主题可能会发生主题失效的情况,这种情况请再重启一次即可。\n\n" + "部分 MIUI 内测和开发版中使用了状态栏主题可能会发生主题失效的情况,这种情况请再重启一次即可。\n\n" +
"重启过程会黑屏并等待进入锁屏重新解锁。" "重启过程会黑屏并等待进入锁屏重新解锁。"
confirmButton { confirmButton {
execShellSu(cmd = "pgrep systemui").also { pid -> execShell(cmd = "pgrep systemui").also { pid ->
if (pid.isNotBlank()) if (pid.isNotBlank())
execShellSu(cmd = "kill -9 $pid") execShell(cmd = "kill -9 $pid")
else toast(msg = "ROOT 权限获取失败") else toast(msg = "ROOT 权限获取失败")
} }
} }
@@ -100,17 +85,10 @@ object SystemUITool {
* 刷新系统界面状态栏与通知图标 * 刷新系统界面状态栏与通知图标
* @param context 实例 * @param context 实例
* @param isRefreshCacheOnly 仅刷新缓存不刷新图标和通知改变 - 默认:否 * @param isRefreshCacheOnly 仅刷新缓存不刷新图标和通知改变 - 默认:否
* @param it 成功后回调 * @param callback 成功后回调
*/ */
fun refreshSystemUI(context: Context? = null, isRefreshCacheOnly: Boolean = false, it: () -> Unit = {}) = runInSafe { fun refreshSystemUI(context: Context? = null, isRefreshCacheOnly: Boolean = false, callback: () -> Unit = {}) = runInSafe {
fun sendMessage() { if (YukiHookAPI.Status.isXposedModuleActive)
(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 { context?.showDialog {
title = "请稍后" title = "请稍后"
progressContent = "正在等待系统界面刷新" progressContent = "正在等待系统界面刷新"
@@ -119,23 +97,29 @@ object SystemUITool {
/** 设置等待延迟 */ /** 设置等待延迟 */
delayedRun(ms = 5000) { delayedRun(ms = 5000) {
if (isWaited) return@delayedRun if (isWaited) return@delayedRun
remindHandlerCallback = null
cancel() cancel()
context.snake(msg = "预计响应超时,建议重启系统界面", actionText = "立即重启") { restartSystemUI(context) } context.snake(msg = "预计响应超时,建议重启系统界面", actionText = "立即重启") { restartSystemUI(context) }
} }
remindHandlerCallback = { isGrasp, isValied -> checkingActivated(context) { isValied ->
remindHandlerCallback = null
cancel()
isWaited = true
when { when {
isGrasp && !isValied -> isValied.not() -> {
cancel()
isWaited = true
context.snake(msg = "请重启系统界面以生效模块更新", actionText = "立即重启") { restartSystemUI(context) } context.snake(msg = "请重启系统界面以生效模块更新", actionText = "立即重启") { restartSystemUI(context) }
else -> it() }
else -> context.dataChannel(SYSTEMUI_PACKAGE_NAME).with {
wait(CALL_MODULE_REFRESH_RESULT) {
cancel()
isWaited = true
callback()
if (it.not()) context.snake(msg = "刷新失败,建议重启系统界面", actionText = "立即重启") { restartSystemUI(context) }
}
put(CALL_HOST_REFRESH_CACHING, isRefreshCacheOnly)
}
} }
} }
sendMessage()
noCancelable() noCancelable()
} ?: sendMessage() }
else context?.snake(msg = "模块没有激活,更改不会生效") else context?.snake(msg = "模块没有激活,更改不会生效")
} }
@@ -144,29 +128,7 @@ object SystemUITool {
* @param context 实例 * @param context 实例
*/ */
fun showNeedRestartSnake(context: Context) = fun showNeedRestartSnake(context: Context) =
if (isXposedModuleActive) if (YukiHookAPI.Status.isXposedModuleActive)
context.snake(msg = "设置需要重启系统界面才能生效", actionText = "立即重启") { restartSystemUI(context) } context.snake(msg = "设置需要重启系统界面才能生效", actionText = "立即重启") { restartSystemUI(context) }
else context.snake(msg = "模块没有激活,更改不会生效") 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,60 @@
/*
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/MIUINativeNotifyIcon
*
* 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/5/30.
*/
package com.fankes.miui.notify.utils.tool
import android.content.Context
import com.fankes.miui.notify.BuildConfig
import com.fankes.miui.notify.utils.factory.openBrowser
import com.fankes.miui.notify.utils.factory.showDialog
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.hook.factory.modulePrefs
import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
/**
* [YukiHookAPI] 的自动推广工具类
*/
object YukiPromoteTool {
/** 推广已读存储键值 */
private val YUKI_PROMOTE_READED = PrefsData("yuki_promote_readed_${BuildConfig.VERSION_NAME}", false)
/**
* 显示推广对话框
* @param context 实例
*/
fun promote(context: Context) {
fun saveReaded() = context.modulePrefs.put(YUKI_PROMOTE_READED, value = true)
if (context.modulePrefs.get(YUKI_PROMOTE_READED).not())
context.showDialog {
title = "面向开发者的推广"
msg = "你想快速拥有一个自己的 Xposed 模块吗,你只需要拥有基础的 Android 开发经验以及使用 Kotlin 编程语言即可。\n\n" +
"快来体验 YukiHookAPI这是一个使用 Kotlin 重新构建的高效 Xposed Hook API助你的开发变得更轻松。"
confirmButton(text = "去看看") {
context.openBrowser(url = "https://github.com/fankes/YukiHookAPI")
saveReaded()
}
cancelButton(text = "我不是开发者") { saveReaded() }
noCancelable()
}
}
}

View File

@@ -316,7 +316,7 @@
android:layout_marginBottom="10dp" android:layout_marginBottom="10dp"
android:alpha="0.6" android:alpha="0.6"
android:lineSpacingExtra="6dp" android:lineSpacingExtra="6dp"
android:text="此选项默认开启MIUI 默认最多只能显示 3 个图标,其余图标将变成省略号,你可以在下方自定义最多显示的图标个数,修改为 0 则只会显示省略号代表图标个数,为防止发生异常,最大限制 100 个,超出的图标可能会被信号或网速指示器遮挡。" android:text="此选项默认开启MIUI 默认最多只能显示 3 个图标,其余图标将变成省略号,你可以在下方自定义最多显示的图标个数,修改为 0 则只会显示省略号代表图标个数,为防止发生异常,最大限制 100 个,超出的图标可能会被信号或网速指示器遮挡。\n请注意可能无法兼容中置挖孔屏的设备。"
android:textColor="@color/colorTextDark" android:textColor="@color/colorTextDark"
android:textSize="12sp" /> android:textSize="12sp" />
@@ -465,15 +465,13 @@
android:elevation="0dp" android:elevation="0dp"
android:gravity="center" android:gravity="center"
android:orientation="vertical" android:orientation="vertical"
android:paddingLeft="10dp" android:paddingTop="15dp">
android:paddingTop="15dp"
android:paddingRight="10dp">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="5dp" android:layout_marginLeft="15dp"
android:layout_marginRight="5dp" android:layout_marginRight="15dp"
android:gravity="center|start"> android:gravity="center|start">
<ImageView <ImageView
@@ -496,8 +494,8 @@
android:id="@+id/notify_icon_fix_switch" android:id="@+id/notify_icon_fix_switch"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="5dp" android:layout_marginLeft="15dp"
android:layout_marginRight="5dp" android:layout_marginRight="15dp"
android:text="启用通知图标优化" android:text="启用通知图标优化"
android:textColor="@color/colorTextGray" android:textColor="@color/colorTextGray"
android:textSize="15sp" /> android:textSize="15sp" />
@@ -506,8 +504,8 @@
android:id="@+id/notify_icon_fix_button" android:id="@+id/notify_icon_fix_button"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="3dp" android:layout_marginLeft="13dp"
android:layout_marginRight="3dp" android:layout_marginRight="13dp"
android:layout_marginBottom="10dp" android:layout_marginBottom="10dp"
android:background="@drawable/bg_button_round" android:background="@drawable/bg_button_round"
android:gravity="center" android:gravity="center"
@@ -520,8 +518,8 @@
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="5dp" android:layout_marginLeft="15dp"
android:layout_marginRight="5dp" android:layout_marginRight="15dp"
android:layout_marginBottom="10dp" android:layout_marginBottom="10dp"
android:alpha="0.6" android:alpha="0.6"
android:lineSpacingExtra="6dp" android:lineSpacingExtra="6dp"
@@ -532,8 +530,8 @@
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="5dp" android:layout_marginLeft="15dp"
android:layout_marginRight="5dp" android:layout_marginRight="15dp"
android:layout_marginBottom="10dp" android:layout_marginBottom="10dp"
android:alpha="0.6" android:alpha="0.6"
android:lineSpacingExtra="6dp" android:lineSpacingExtra="6dp"
@@ -541,12 +539,119 @@
android:textColor="@color/colorTextDark" android:textColor="@color/colorTextDark"
android:textSize="12sp" /> android:textSize="12sp" />
<LinearLayout
android:id="@+id/notify_icon_custom_corner_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_marginBottom="5dp"
android:gravity="center|start"
android:text="调整通知栏中的图标边框圆角大小"
android:textAllCaps="false"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<androidx.appcompat.widget.AppCompatSeekBar
android:id="@+id/notify_icon_custom_corner_seekbar"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginTop="5dp"
android:max="15"
android:min="0"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:progress="15" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
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_icon_custom_corner_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="15 dp"
android:textColor="@color/colorTextGray"
android:textSize="15sp"
android:textStyle="bold" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="你可以拖拽滑动条来调整通知栏中图标的边框圆角大小,仅支持 Android 12 风格以及 MIUI 经典样式的通知图标。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/notify_icon_force_app_icon_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:orientation="vertical">
<com.fankes.miui.notify.ui.view.MaterialSwitch
android:id="@+id/notify_icon_force_app_icon_switch"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_marginBottom="5dp"
android:text="通知栏中的图标强制为 APP 图标"
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="此选项默认关闭,开启后下拉通知栏中的通知图标将会被替换为 APP 自身图标,但是不会更改状态栏中的通知图标,这是一个破坏原生通知图标的行为,仅针对部分有需要的用户而添加,我们不推荐开启这个功能,请根据个人偏好进行选择是否需要开启。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/notify_icon_fix_notify_item" android:id="@+id/notify_icon_fix_notify_item"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="5dp" android:layout_marginLeft="15dp"
android:layout_marginRight="5dp" android:layout_marginRight="15dp"
android:orientation="vertical"> android:orientation="vertical">
<com.fankes.miui.notify.ui.view.MaterialSwitch <com.fankes.miui.notify.ui.view.MaterialSwitch
@@ -573,8 +678,8 @@
android:id="@+id/notify_icon_auto_sync_item" android:id="@+id/notify_icon_auto_sync_item"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="5dp" android:layout_marginLeft="15dp"
android:layout_marginRight="5dp" android:layout_marginRight="15dp"
android:animateLayoutChanges="true" android:animateLayoutChanges="true"
android:orientation="vertical"> android:orientation="vertical">
@@ -780,7 +885,7 @@
android:layout_marginBottom="10dp" android:layout_marginBottom="10dp"
android:alpha="0.8" android:alpha="0.8"
android:lineSpacingExtra="10dp" android:lineSpacingExtra="10dp"
android:text="Q.哪些是已知问题?\nA.以下是问题描述列表:\n(1) 动态小图标可能会在高版本系统中闪烁,这是 MIUI 自身就存在的问题,后期只能等官方修复。\n(2) 请始终保持最新版本的 LSPosed旧版本可能会出现 Hook 不生效的问题,若最新版本依然不生效请在作用域中长按“系统界面”(“系统 UI”)选择重新优化。\n(3) 建议最低从 MIUI 12.5 “2021-5-18” 开发版以后开始使用,之前的版本可能或多或少存在 MIUI 自身 BUG 不生效、黑白块问题,将不再进行适配。" android:text="Q.哪些是已知问题?\nA.以下是问题描述列表:\n(1) 动态小图标可能会在高版本系统中闪烁,这是 MIUI 强行设置 APP 图标的问题,暂时没有找到解决方案,强行破坏修复方式会导致原生动画出现问题,后期有解决方案再研究。\n(2) 请始终保持最新版本的 LSPosed旧版本可能会出现 Hook 不生效的问题,若最新版本依然不生效请在作用域中长按“系统界面”(“系统 UI”)选择重新优化。\n(3) 建议最低从 MIUI 12.5 “2021-5-18” 开发版以后开始使用,之前的版本可能或多或少存在 MIUI 自身 BUG 不生效、黑白块问题,将不再进行适配。"
android:textColor="@color/colorTextDark" android:textColor="@color/colorTextDark"
android:textSize="12sp" /> android:textSize="12sp" />

View File

@@ -14,7 +14,7 @@
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/dia_icon_filter_input_edit" android:id="@+id/icon_filters_edit"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="end" android:ellipsize="end"

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -19,32 +20,40 @@
android:text="在线规则将不定期更新,建议定期同步列表以适配更多 APP若无法同步请自行寻找解决方法或魔法上网。" android:text="在线规则将不定期更新,建议定期同步列表以适配更多 APP若无法同步请自行寻找解决方法或魔法上网。"
android:textSize="14sp" /> android:textSize="14sp" />
<com.google.android.material.radiobutton.MaterialRadioButton <RadioGroup
android:id="@+id/dia_sf_rd1" android:layout_width="match_parent"
android:layout_width="wrap_content" android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:text="从 FastGit 获取" />
<com.google.android.material.radiobutton.MaterialRadioButton <com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/dia_sf_rd2" android:id="@+id/source_radio_1"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="从 Github Raw 获取" /> android:text="从 FastGit 获取"
app:buttonTint="@color/colorPrimaryAccent" />
<com.google.android.material.radiobutton.MaterialRadioButton <com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/dia_sf_rd3" android:id="@+id/source_radio_2"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="从自定义地址获取" /> android:text="从 Github Raw 获取"
app:buttonTint="@color/colorPrimaryAccent" />
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/source_radio_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="从自定义地址获取"
app:buttonTint="@color/colorPrimaryAccent" />
</RadioGroup>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/dia_sf_text_lin" android:id="@+id/source_from_text_lin"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="gone"> android:visibility="gone">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/dia_sf_text" android:id="@+id/source_url_edit"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="end" android:ellipsize="end"

View File

@@ -9,12 +9,23 @@
android:paddingRight="15dp" android:paddingRight="15dp"
tools:ignore="HardcodedText"> 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 <com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/dia_sfs_input_edit" android:id="@+id/json_rule_edit"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="150dp" android:layout_height="150dp"
android:ellipsize="end" android:ellipsize="end"

View File

@@ -14,7 +14,7 @@
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/dia_status_icon_count_input_edit" android:id="@+id/icon_count_edit"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:digits="0123456789" android:digits="0123456789"

View File

@@ -2,15 +2,16 @@
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="Theme.MIUINativeNotifyIcon" parent="Theme.Material3.DayNight"> <style name="Theme.MIUINativeNotifyIcon" parent="Theme.Material3.DayNight">
<!-- Primary brand color. --> <!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item> <item name="colorPrimary">@color/colorPrimaryAccent</item>
<item name="colorPrimaryVariant">@color/purple_700</item> <item name="colorPrimaryVariant">@color/colorPrimaryAccent</item>
<item name="colorOnPrimary">@color/black</item> <item name="colorOnPrimary">@color/colorPrimaryAccent</item>
<!-- Secondary brand color. --> <!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item> <item name="colorSecondary">@color/colorPrimaryAccent</item>
<item name="colorSecondaryVariant">@color/teal_200</item> <item name="colorSecondaryVariant">@color/colorPrimaryAccent</item>
<item name="colorOnSecondary">@color/black</item> <item name="colorOnSecondary">@color/colorPrimaryAccent</item>
<!-- Status bar color. --> <!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item> <item name="android:statusBarColor">@color/black</item>
<item name="android:windowLightStatusBar">false</item>
<!-- Customize your theme here. --> <!-- Customize your theme here. -->
</style> </style>
</resources> </resources>

View File

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

View File

@@ -2,15 +2,16 @@
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="Theme.MIUINativeNotifyIcon" parent="Theme.Material3.DayNight"> <style name="Theme.MIUINativeNotifyIcon" parent="Theme.Material3.DayNight">
<!-- Primary brand color. --> <!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item> <item name="colorPrimary">@color/colorPrimaryAccent</item>
<item name="colorPrimaryVariant">@color/purple_700</item> <item name="colorPrimaryVariant">@color/colorPrimaryAccent</item>
<item name="colorOnPrimary">@color/teal_700</item> <item name="colorOnPrimary">@color/colorPrimaryAccent</item>
<!-- Secondary brand color. --> <!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item> <item name="colorSecondary">@color/colorPrimaryAccent</item>
<item name="colorSecondaryVariant">@color/teal_700</item> <item name="colorSecondaryVariant">@color/colorPrimaryAccent</item>
<item name="colorOnSecondary">@color/black</item> <item name="colorOnSecondary">@color/colorPrimaryAccent</item>
<!-- Status bar color. --> <!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item> <item name="android:statusBarColor">@color/white</item>
<item name="android:windowLightStatusBar">true</item>
<!-- Customize your theme here. --> <!-- Customize your theme here. -->
</style> </style>
</resources> </resources>

View File

@@ -1,12 +1,13 @@
plugins { plugins {
id 'com.android.application' version '7.1.2' apply false id 'com.android.application' version '7.2.1' apply false
id 'com.android.library' version '7.1.2' apply false id 'com.android.library' version '7.2.1' apply false
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false id 'org.jetbrains.kotlin.android' version '1.7.0' apply false
} }
ext { ext {
appVersionName = "2.3" appVersionName = "2.8"
appVersionCode = 28 appVersionCode = 34
enableR8 = true
} }
task clean(type: Delete) { task clean(type: Delete) {

View File

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

View File

@@ -1,6 +1,6 @@
#Tue Feb 15 02:09:05 CST 2022 #Wed May 25 04:36:53 CST 2022
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME