57 Commits
2.2 ... 2.6

Author SHA1 Message Date
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
822e988c16 Update version to 2.3 2022-03-29 23:50:37 +08:00
fc728a75a4 Update version to 2.3 2022-03-29 23:50:09 +08:00
256e2ebfce 优化 Android 12 风格通知图标深色模式的颜色 2022-03-29 23:47:21 +08:00
be4b952f62 修复折叠情况下的经典通知图标无变化问题,添加安卓 12 以下系统经典通知情况下图标颜色实时跟随壁纸功能 2022-03-29 23:14:57 +08:00
c7263c6d94 Fix a receiver bug 2022-03-29 21:46:20 +08:00
4733578912 Update YukiHookAPI 2022-03-29 21:05:49 +08:00
87879301f0 Update YukiHookAPI 2022-03-29 21:04:03 +08:00
c9be72e5f7 Update version to 2.2 2022-03-28 14:16:58 +08:00
25 changed files with 1052 additions and 627 deletions

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/license-AGPL3.0-blue)](https://github.com/fankes/MIUINativeNotifyIcon/blob/master/LICENSE)
[![Blank](https://img.shields.io/badge/version-v2.2-green)](https://github.com/fankes/MIUINativeNotifyIcon/releases)
[![Blank](https://img.shields.io/badge/version-v2.6-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/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/>
<img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/app/src/main/ic_launcher-playstore.png" width = "100" height = "100"/>
<br/>
Fix the native notification bar icon function abandoned by the MIUI development team.<br/>
修复被 MIUI 开发组丢弃的原生通知图标,支持 MIUI 12、12.5、13 以及最新版本。
# Developer
## Developer
[酷安 @星夜不荟](http://www.coolapk.com/u/876977)
# 适配说明
## 适配说明
- 此模块仅支持 <b>LSPosed</b>(作用域“系统界面”)、<b>~~EdXposed(随时停止支持)~~</b>、不支持<b>太极、无极</b>
- 请确保你使用的是 MIUI 官方版本,任何第三方官改包发生的问题,开发者没有义务去解决和修复,请自求多福
- 目前最低支持基于 Android 9 版本的 MIUI 12 或 MIUI 12.5(最低建议)
- 建议最低从 MIUI 12.5 `2021-5-18` 开发版以后开始使用模块,之前的版本可能或多或少存在 MIUI 自身 BUG 不生效、黑白块的问题
- 请始终保持最新版本的 LSPosed旧版本可能会出现 Hook 不生效的问题,若最新版本依然不生效请在作用域中长按“系统界面”(“系统 UI”)选择重新优化
# 请勿用于非法用途
## 请勿用于非法用途
- 本模块完全开源免费,如果好用你可以打赏支持开发,但是请不要用于非法用途。
- 本模块发布地址仅有 [Xposed-Modules-Repo](https://github.com/Xposed-Modules-Repo/com.fankes.miui.notify/releases)、
[Release](https://github.com/fankes/MIUINativeNotifyIcon/releases)
及 [蓝奏云](https://fankes.lanzouy.com/b030o2e8h),从其他非正规渠道下载到的版本或对您造成任何影响均与我们无关。
[Release](https://github.com/fankes/MIUINativeNotifyIcon/releases)、[蓝奏云](https://fankes.lanzouy.com/b030o2e8h) 及**酷安应用市场**
,从其他非正规渠道下载到的版本或对您造成任何影响均与我们无关。
# 贡献通知图标优化名单
## 贡献通知图标优化名单
此项目是 `AndroidNotifyIconAdapt` 项目的一部分,详情请参考下方。<br/>
此项目是 `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 修复了黑白块图标的问题。
<br/>
# 探索历程
## 探索历程
原生 Android 的小图标和通知图标具有状态性。<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/>
<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/>
不曾记得是什么版本开始MIUI 把通知图标改成了这个鬼样子,寻找开发组提案也是无人问津,最后转念一想,自己干吧。<br/><br/>
不曾记得是什么版本开始MIUI 把通知图标改成了这个鬼样子,寻找开发组提案也是无人问津,最后转念一想,自己干吧。
由于目前大量通知图标都来自 `MIPUSH` 发出的营销通知,而 `MIPUSH` 的图标都是统一的彩色应用图标,很多应用也没有适配这一特性, 在通知栏广告满天飞的情况下MIUI
选择放弃原生通知功能,而做出这种违反原生通知规则的做法,而这些彩色图标被设置为单色调图标,也确实会发生黑白块的问题,但是同时又会破坏遵守规范的图标。<br/><br/>
选择放弃原生通知功能,而做出这种违反原生通知规则的做法,而这些彩色图标被设置为单色调图标,也确实会发生黑白块的问题,但是同时又会破坏遵守规范的图标。
真的没有办法了吗?在不断探索下,我找到了原生支持色彩判断的类。
```kotlin
@@ -74,18 +85,32 @@ com.android.internal.util.ContrastColorUtil
ContrastColorUtil.getInstance().isGrayscaleIcon(drawable)
```
问题就被解决了,顺便修了一下被 MIUI 破坏的通知图标以及优化了一下应用本身方块图标的圆角......<br/><br/>
最后,我想大声问一句 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 再不重写,怕是永远会变成安卓之光。雷军,金凡!!
# 许可证
## 捐赠支持
- 工作不易,无意外情况此项目将继续维护下去,提供更多可能,欢迎打赏。<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)
@@ -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/>.
```
Powered by [YukiHookAPI](https://github.com/fankes/YukiHookAPI)<br/><br/>
Powered by [YukiHookAPI](https://github.com/fankes/YukiHookAPI)
版权所有 © 2019-2022 Fankes Studio(qzmmcn@163.com)

View File

@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.devtools.ksp' version '1.6.10-1.0.2'
id 'com.google.devtools.ksp' version '1.6.21-1.0.5'
}
android {
@@ -25,64 +25,47 @@ android {
versionCode rootProject.ext.appVersionCode
versionName rootProject.ext.appVersionName
kotlinOptions {
freeCompilerArgs = [
'-Xno-param-assertions',
'-Xno-call-assertions',
'-Xno-receiver-assertions'
]
}
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled true
minifyEnabled rootProject.ext.enableR8
signingConfig signingConfigs.debug
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = '1.8'
jvmTarget = '11'
freeCompilerArgs = [
'-Xno-param-assertions',
'-Xno-call-assertions',
'-Xno-receiver-assertions'
]
}
buildFeatures {
viewBinding true
}
}
/** 移除无效耗时 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
lintOptions {
checkReleaseBuilds false
}
}
dependencies {
compileOnly 'de.robv.android.xposed:api:82'
implementation 'com.highcapable.yukihookapi:api:1.0.67'
ksp 'com.highcapable.yukihookapi:ksp-xposed:1.0.67'
implementation 'com.highcapable.yukihookapi:api:1.0.91'
ksp 'com.highcapable.yukihookapi:ksp-xposed:1.0.91'
implementation "com.github.topjohnwu.libsu:core:3.1.2"
implementation 'androidx.annotation:annotation:1.3.0'
implementation 'com.geyifeng.immersionbar:immersionbar:3.2.0'
implementation 'com.geyifeng.immersionbar:immersionbar-ktx:3.2.0'
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'com.google.android.material:material:1.6.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

View File

@@ -20,7 +20,6 @@
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-dontwarn
-ignorewarnings
-optimizationpasses 10
-dontusemixedcaseclassnames
@@ -35,13 +34,6 @@
-renamesourcefileattribute P
-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 {
public static *** throwUninitializedProperty(...);
public static *** throwUninitializedPropertyAccessException(...);

View File

@@ -24,24 +24,13 @@
package com.fankes.miui.notify.application
import android.app.Application
import androidx.appcompat.app.AppCompatDelegate
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication
class MNNApplication : Application() {
companion object {
/** 全局静态实例 */
private var context: MNNApplication? = null
/** 调用全局静态实例 */
val appContext get() = context ?: error("App is death")
}
class MNNApplication : ModuleApplication() {
override fun onCreate() {
super.onCreate()
/** 设置静态实例 */
context = this
/** 跟随系统夜间模式 */
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}_202203281323"
/** 当前模块的版本校验标签 */
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,6 +32,7 @@ object DataConst {
val ENABLE_HIDE_ICON = PrefsData("_hide_icon", 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_FORCE_APP_ICON = PrefsData("_notify_icon_force_app_icon", false)
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_NOTIFY_ICON_FIX_AUTO = PrefsData("_enable_notify_icon_fix_auto", true)

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

View File

@@ -20,13 +20,14 @@
*
* This file is Created by fankes on 2022/3/25.
*/
@file:Suppress("StaticFieldLeak")
package com.fankes.miui.notify.hook.entity
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.app.WallpaperManager
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.Outline
@@ -34,14 +35,15 @@ import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.os.Build
import android.os.SystemClock
import android.service.notification.StatusBarNotification
import android.view.View
import android.view.ViewGroup
import android.view.ViewOutlineProvider
import android.widget.ImageView
import androidx.core.graphics.drawable.toBitmap
import androidx.core.view.children
import com.fankes.miui.notify.bean.IconDataBean
import com.fankes.miui.notify.const.Const
import com.fankes.miui.notify.data.DataConst
import com.fankes.miui.notify.hook.HookConst.SYSTEMUI_PACKAGE_NAME
import com.fankes.miui.notify.hook.factory.isAppNotifyHookAllOf
@@ -51,12 +53,10 @@ import com.fankes.miui.notify.utils.drawable.drawabletoolbox.DrawableBuilder
import com.fankes.miui.notify.utils.factory.*
import com.fankes.miui.notify.utils.tool.BitmapCompatTool
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.entity.YukiBaseHooker
import com.highcapable.yukihookapi.hook.factory.field
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.factory.*
import com.highcapable.yukihookapi.hook.log.loggerD
import com.highcapable.yukihookapi.hook.log.loggerW
import com.highcapable.yukihookapi.hook.type.android.*
@@ -66,69 +66,80 @@ import com.highcapable.yukihookapi.hook.type.java.IntType
/**
* 系统界面核心 Hook 类
*/
class SystemUIHooker : YukiBaseHooker() {
object SystemUIHooker : YukiBaseHooker() {
companion object {
/** MIUI 新版本存在的类 */
private const val SystemUIApplicationClass = "$SYSTEMUI_PACKAGE_NAME.SystemUIApplication"
/** MIUI 新版本存在的类 */
private const val SystemUIApplicationClass = "${SYSTEMUI_PACKAGE_NAME}.SystemUIApplication"
/** MIUI 新版本存在的类 */
private const val NotificationHeaderViewWrapperInjectorClass =
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.wrapper.NotificationHeaderViewWrapperInjector"
/** MIUI 新版本存在的类 */
private const val NotificationHeaderViewWrapperInjectorClass =
"${SYSTEMUI_PACKAGE_NAME}.statusbar.notification.row.wrapper.NotificationHeaderViewWrapperInjector"
/** MIUI 新版本存在的类 */
private const val MiuiNotificationViewWrapperClass =
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.wrapper.MiuiNotificationViewWrapper"
/** 原生存在的类 */
private const val ContrastColorUtilClass = "com.android.internal.util.ContrastColorUtil"
/** MIUI 新版本存在的类 */
private const val MiuiNotificationChildrenContainerClass =
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.stack.MiuiNotificationChildrenContainer"
/** 原生存在的类 */
private const val StatusBarIconViewClass = "${SYSTEMUI_PACKAGE_NAME}.statusbar.StatusBarIconView"
/** 原生存在的类 */
private const val NotificationChildrenContainerClass =
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.stack.NotificationChildrenContainer"
/** 原生存在的类 */
private const val NotificationIconContainerClass = "${SYSTEMUI_PACKAGE_NAME}.statusbar.phone.NotificationIconContainer"
/** 原生存在的类 */
private const val ContrastColorUtilClass = "com.android.internal.util.ContrastColorUtil"
/** 原生存在的类 */
private const val PluginManagerImplClass = "${SYSTEMUI_PACKAGE_NAME}.shared.plugins.PluginManagerImpl"
/** 原生存在的类 */
private const val StatusBarIconViewClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.StatusBarIconView"
/** MIUI 存在的类 - 旧版本没有 */
private const val MiuiNotificationViewWrapperClass =
"${SYSTEMUI_PACKAGE_NAME}.statusbar.notification.row.wrapper.MiuiNotificationViewWrapper"
/** 原生存在的类 */
private const val NotificationIconContainerClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.phone.NotificationIconContainer"
/** 根据多个版本存在不同的包名相同的类 */
private val MiuiClockClass = VariousClass(
"${SYSTEMUI_PACKAGE_NAME}.statusbar.views.MiuiClock",
"${SYSTEMUI_PACKAGE_NAME}.statusbar.policy.MiuiClock"
)
/** 原生存在的类 */
private const val PluginManagerImplClass = "$SYSTEMUI_PACKAGE_NAME.shared.plugins.PluginManagerImpl"
/** 根据多个版本存在不同的包名相同的类 */
private val ExpandableNotificationRowClass = VariousClass(
"${SYSTEMUI_PACKAGE_NAME}.statusbar.notification.row.ExpandableNotificationRow",
"${SYSTEMUI_PACKAGE_NAME}.statusbar.ExpandableNotificationRow"
)
/** 根据多个版本存在不同的包名相同的类 */
private val MiuiClockClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.views.MiuiClock",
"$SYSTEMUI_PACKAGE_NAME.statusbar.policy.MiuiClock"
)
/** 根据多个版本存在不同的包名相同的类 */
private val NotificationViewWrapperClass = VariousClass(
"${SYSTEMUI_PACKAGE_NAME}.statusbar.notification.row.wrapper.NotificationViewWrapper",
"${SYSTEMUI_PACKAGE_NAME}.statusbar.notification.NotificationViewWrapper"
)
/** 根据多个版本存在不同的包名相同的类 */
private val StatusBarNotificationPresenterClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.phone.StatusBarNotificationPresenter",
"$SYSTEMUI_PACKAGE_NAME.statusbar.phone.StatusBar"
)
/** 根据多个版本存在不同的包名相同的类 */
private val NotificationHeaderViewWrapperClass = VariousClass(
"${SYSTEMUI_PACKAGE_NAME}.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper",
"${SYSTEMUI_PACKAGE_NAME}.statusbar.notification.NotificationHeaderViewWrapper"
)
/** 根据多个版本存在不同的包名相同的类 */
private val ExpandableNotificationRowClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.ExpandableNotificationRow",
"$SYSTEMUI_PACKAGE_NAME.statusbar.ExpandableNotificationRow"
)
/** 根据多个版本存在不同的包名相同的类 */
private val NotificationUtilClass = VariousClass(
"${SYSTEMUI_PACKAGE_NAME}.statusbar.notification.NotificationUtil",
"${SYSTEMUI_PACKAGE_NAME}.miui.statusbar.notification.NotificationUtil"
)
/** 根据多个版本存在不同的包名相同的类 */
private val NotificationViewWrapperClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.wrapper.NotificationViewWrapper",
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationViewWrapper"
)
/** 根据多个版本存在不同的包名相同的类 */
private val ExpandedNotificationClass = VariousClass(
"${SYSTEMUI_PACKAGE_NAME}.statusbar.notification.ExpandedNotification",
"${SYSTEMUI_PACKAGE_NAME}.miui.statusbar.ExpandedNotification"
)
}
/** 根据多个版本存在不同的包名相同的类 */
private val NotificationHeaderViewWrapperClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper",
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationHeaderViewWrapper"
)
/** 根据多个版本存在不同的包名相同的类 */
private val NotificationUtilClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationUtil",
"$SYSTEMUI_PACKAGE_NAME.miui.statusbar.notification.NotificationUtil"
)
/** 根据多个版本存在不同的包名相同的类 */
private val ExpandedNotificationClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.ExpandedNotification",
"$SYSTEMUI_PACKAGE_NAME.miui.statusbar.ExpandedNotification"
)
/** 缓存的通知图标优化数组 */
private var iconDatas = ArrayList<IconDataBean>()
@@ -139,73 +150,14 @@ class SystemUIHooker : YukiBaseHooker() {
/** 是否已经使用过缓存刷新功能 */
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>()
/** 是否已经注册广播 */
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
}
/** 仅监听一次主题壁纸颜色变化 */
private var isWallpaperColorListenerSetUp = false
/**
* 是否启用通知图标优化功能
@@ -232,7 +184,7 @@ class SystemUIHooker : YukiBaseHooker() {
}.get(it.method {
name = "getInstance"
param(ContextClass)
}.get().invoke(context)).callBoolean(drawable)
}.get().invoke(context)).boolean(drawable)
}
} else BitmapCompatTool.isGrayscaleDrawable(drawable)
@@ -253,19 +205,10 @@ class SystemUIHooker : YukiBaseHooker() {
get() = safeOfFalse {
NotificationUtilClass.clazz.hasMethod {
name = "ignoreStatusBarIconColor"
param(ExpandedNotificationClass.clazz)
param(ExpandedNotificationClass)
}
}
/**
* 获取当前通知栏的样式
*
* - ❗新版本可能不存在这个方法
* @return [Boolean]
*/
private val isShowMiuiStyle
get() = safeOfFalse { NotificationUtilClass.clazz.method { name = "showMiuiStyle" }.get().invoke() ?: false }
/**
* 处理为圆角图标
* @return [Drawable]
@@ -351,18 +294,31 @@ class SystemUIHooker : YukiBaseHooker() {
*/
private val globalContext
get() = safeOfNull {
SystemUIApplicationClass.clazz.method { name = "getContext" }.ignoredError().get().invoke<Context>()
SystemUIApplicationClass.clazz.method { name = "getContext" }.ignoredError().get().invoke<Context?>() ?: appContext
}
/**
* 注册主题壁纸改变颜色监听
*
* - 仅限在 Android 12 以下注册
* @param view 实例
*/
private fun registerWallpaperColorChanged(view: View) = runInSafe {
if (isWallpaperColorListenerSetUp.not() && isUpperOfAndroidS.not()) view.apply {
WallpaperManager.getInstance(context).addOnColorsChangedListener({ _, _ -> refreshNotificationIcons() }, handler)
}
isWallpaperColorListenerSetUp = true
}
/** 刷新状态栏小图标 */
private fun refreshStatusBarIcons() = runInSafe {
StatusBarIconViewClass.clazz.field { name = "mNotification" }.also { result ->
statusBarIconViews.takeIf { it.isNotEmpty() }?.forEach {
notificationIconContainer?.children?.forEach {
/** 得到通知实例 */
val nf = result.of<StatusBarNotification>(it) ?: return
val nf = result.get(it).cast<StatusBarNotification>() ?: return
/** 刷新状态栏图标 */
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) }
}
}
}
@@ -370,13 +326,11 @@ class SystemUIHooker : YukiBaseHooker() {
/** 刷新通知小图标 */
private fun refreshNotificationIcons() = runInSafe {
(if (hasHandleHeaderViews)
NotificationHeaderViewWrapperClass.clazz.method { name = "handleHeaderViews" }
else NotificationHeaderViewWrapperClass.clazz.method { name = "resolveHeaderViews" }).also { result ->
notificationViewWrappers.takeIf { it.isNotEmpty() }?.forEach { result.get(it).call() }
}
MiuiNotificationViewWrapperClass.clazz.method { name = "handleViews" }.ignoredError().also { result ->
miuiNotificationViewWrappers.takeIf { it.isNotEmpty() }?.forEach { result.get(it).call() }
notificationPresenter?.current {
method {
name = "updateNotificationsOnDensityOrFontScaleChanged"
emptyParam()
}.call()
}
}
@@ -446,6 +400,35 @@ class SystemUIHooker : YukiBaseHooker() {
isExpanded: Boolean = true,
isUseAndroid12Style: Boolean = isUpperOfAndroidS,
) = 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 的版本迭代不规范性可能是空的 */
expandedNf?.let { notifyInstance ->
@@ -489,54 +472,45 @@ class SystemUIHooker : YukiBaseHooker() {
/** 打印日志 */
printLogcat(tag = "NotifyIcon", context, notifyInstance, isCustom = customIcon != null, isGrayscaleIcon)
/** 处理自定义通知图标优化 */
if (customIcon != null) iconImageView.apply {
/** 设置不要裁切到边界 */
clipToOutline = false
/** 设置自定义小图标 */
setImageBitmap(customIcon)
/** 上色 */
setColorFilter(if (isUseAndroid12Style || customIconColor == 0) supportColor else customIconColor)
/** Android 12 设置图标外圈颜色 */
if (isUseAndroid12Style && customIconColor != 0)
background = DrawableBuilder().rounded().solidColor(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 {
when {
prefs.get(DataConst.ENABLE_NOTIFY_ICON_FORCE_APP_ICON) && isEnableHookColorNotifyIcon(isHooking = false) ->
setDefaultNotifyIcon(context.findAppIcon(notifyInstance.compatOpPkgName))
customIcon != null -> iconImageView.apply {
/** 设置不要裁切到边界 */
clipToOutline = false
/** 设置图标着色 */
setColorFilter(supportColor)
/** 设置自定义小图标 */
setImageBitmap(customIcon)
/** 上色 */
setColorFilter(if (isUseAndroid12Style || customIconColor == 0) supportColor else customIconColor)
/** Android 12 设置图标外圈颜色 */
if (isUseAndroid12Style) background =
DrawableBuilder().rounded().solidColor(if (hasIconColor) iconColor else context.systemAccentColor).build()
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.apply {
/** 重新设置图标 */
setImageDrawable(notifyInstance.compatPushingIcon(context, iconDrawable))
/** 设置裁切到边界 */
clipToOutline = true
/** 设置一个圆角轮廓裁切 */
outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View, out: Outline) {
out.setRoundRect(
0, 0,
view.width, view.height, 5.dpFloat(context)
)
}
else -> {
/** 重新设置图标 - 防止系统更改它 */
iconImageView.setImageDrawable(iconDrawable)
/** 判断如果是灰度图标就给他设置一个白色颜色遮罩 */
if (isGrayscaleIcon) iconImageView.apply {
/** 设置不要裁切到边界 */
clipToOutline = false
/** 设置图标着色 */
setColorFilter(supportColor)
/** Android 12 设置图标外圈颜色 */
(if (hasIconColor) iconColor else context.systemAccentColor).also {
if (isUseAndroid12Style)
background = DrawableBuilder()
.rounded()
.solidColor(if (context.isSystemInDarkMode) it.brighter else it)
.build()
}
}
if (isUseAndroid12Style) {
/** 清除原生的背景边距 */
setPadding(0, 0, 0, 0)
/** 清除原生的主题色背景圆圈颜色 */
background = null
}
/** 清除遮罩颜色 */
colorFilter = null
/** 设置原生的背景边距 */
if (isUseAndroid12Style) setPadding(4.dp(context), 4.dp(context), 4.dp(context), 4.dp(context))
} else setDefaultNotifyIcon(notifyInstance.compatPushingIcon(context, iconDrawable))
}
}
}
@@ -572,10 +546,10 @@ class SystemUIHooker : YukiBaseHooker() {
}
/**
* 从 [NotificationViewWrapperClass] 中获取 [StatusBarNotification]
* @return [Pair] - ([Boolean] 通知是否展开,[StatusBarNotification] 通知实例)
* 从 [NotificationViewWrapperClass] 中获取 [ExpandableNotificationRowClass]
* @return [Pair] - ([Boolean] 通知是否展开,[Any] 通知 Row 实例)
*/
private fun Any.getSbnPair(): Pair<Boolean, StatusBarNotification?> {
private fun Any.getRowPair(): Pair<Boolean, Any?> {
/** 通知是否展开 */
var isExpanded = false
@@ -583,16 +557,25 @@ class SystemUIHooker : YukiBaseHooker() {
* 从父类中得到 mRow 变量 - [ExpandableNotificationRowClass]
* 获取其中的得到通知方法
*/
val expandedNf = ExpandableNotificationRowClass.clazz
val row = NotificationViewWrapperClass.clazz.field {
name = "mRow"
}.get(this).self?.also {
isExpanded = ExpandableNotificationRowClass.clazz.method {
name = "isExpanded"
returnType = BooleanType
}.get(it).boolean()
}
return Pair(isExpanded, row)
}
/**
* 从 [ExpandableNotificationRowClass] 中获取 [StatusBarNotification]
* @return [StatusBarNotification] or null
*/
private fun Any?.getSbn() =
ExpandableNotificationRowClass.clazz
.method { name = "getEntry" }
.get(NotificationViewWrapperClass.clazz.field {
name = "mRow"
}.get(this).self?.also {
isExpanded = ExpandableNotificationRowClass.clazz.method {
name = "isExpanded"
returnType = BooleanType
}.get(it).callBoolean()
}).call()?.let {
.get(this).call()?.let {
it.javaClass.method {
name = "getSbn"
}.get(it).invoke<StatusBarNotification>()
@@ -600,7 +583,13 @@ class SystemUIHooker : YukiBaseHooker() {
.method { name = "getStatusBarNotification" }
.get(NotificationViewWrapperClass.clazz.field { name = "mRow" }.get(this).self)
.invoke<StatusBarNotification>()
return Pair(isExpanded, expandedNf)
/** 注册 */
private fun register() {
/** 解锁后重新刷新状态栏图标防止系统重新设置它 */
onAppLifecycle { registerReceiver(Intent.ACTION_USER_PRESENT) { _, _ -> if (isUsingCachingMethod) refreshStatusBarIcons() } }
/** 刷新图标缓存 */
SystemUITool.Host.onRefreshSystemUI(param = this) { recachingPrefs(it) }
}
/** 缓存图标数据 */
@@ -617,17 +606,25 @@ class SystemUIHooker : YukiBaseHooker() {
/**
* 刷新缓存数据
* @param isRefreshCacheOnly 仅刷新缓存不刷新图标和通知改变 - 默认:否
* @return [Boolean] 是否成功
*/
private fun recachingPrefs(isRefreshCacheOnly: Boolean = false) {
isUsingCachingMethod = true
prefs.clearCache()
cachingIconDatas()
if (isRefreshCacheOnly) return
refreshStatusBarIcons()
refreshNotificationIcons()
private fun recachingPrefs(isRefreshCacheOnly: Boolean = false): Boolean {
/** 必要的延迟防止 Sp 存储不刷新 */
SystemClock.sleep(100)
/** 获取可读写状态 */
return prefs.isXSharePrefsReadable.also {
isUsingCachingMethod = true
prefs.clearCache()
cachingIconDatas()
if (isRefreshCacheOnly) return@also
refreshStatusBarIcons()
refreshNotificationIcons()
}
}
override fun onHook() {
/** 注册 */
register()
/** 缓存图标数据 */
cachingIconDatas()
/** 注入 MIUI 自己增加的一个工具类 */
@@ -636,38 +633,34 @@ class SystemUIHooker : YukiBaseHooker() {
injectMember {
method {
name = "shouldSubstituteSmallIcon"
param(ExpandedNotificationClass.clazz)
param(ExpandedNotificationClass)
}
/**
* MIUI 12 在非原生样式下 MIPUSH 的图标着色异常
* 所以判断是 MIUI 样式就停止 Hook 状态栏图标
*/
replaceAny { hasIgnoreStatusBarIconColor.not() && isShowMiuiStyle }
replaceToFalse()
}
/** 强制回写系统的状态栏图标样式为原生 */
injectMember {
var isUseLegacy = false
method {
name = "getSmallIcon"
param(ExpandedNotificationClass.clazz, IntType)
param(ExpandedNotificationClass, IntType)
}.remedys {
method {
name = "getSmallIcon"
param(ExpandedNotificationClass.clazz)
param(ExpandedNotificationClass)
}
method {
name = "getSmallIcon"
param(ContextClass, ExpandedNotificationClass.clazz)
param(ContextClass, ExpandedNotificationClass)
}.onFind { isUseLegacy = true }
}
afterHook {
(globalContext ?: firstArgs())?.also { context ->
val expandedNf = args(if (isUseLegacy) 1 else 0).of<StatusBarNotification?>()
(globalContext ?: args().first().cast())?.also { context ->
val expandedNf = args(if (isUseLegacy) 1 else 0).cast<StatusBarNotification?>()
/** Hook 状态栏小图标 */
compatStatusIcon(
context = context,
expandedNf,
(result as Icon).loadDrawable(context)
result<Icon>()?.loadDrawable(context)
).also { pair -> if (pair.second) result = Icon.createWithBitmap(pair.first?.toBitmap()) }
}
}
@@ -681,7 +674,7 @@ class SystemUIHooker : YukiBaseHooker() {
afterHook {
instance<ImageView>().also {
if (hasIgnoreStatusBarIconColor(it.context, field { name = "mNotification" }
.of<StatusBarNotification>(instance))) it.apply {
.get(instance).cast<StatusBarNotification>())) it.apply {
alpha = 1f
colorFilter = null
} else it.apply {
@@ -690,7 +683,7 @@ class SystemUIHooker : YukiBaseHooker() {
* 图标在任何场景下跟随状态栏其它图标保持半透明
* MIUI 12 进行单独判断
*/
field { name = "mCurrentSetColor" }.ofInt(instance).also { color ->
field { name = "mCurrentSetColor" }.get(instance).int().also { color ->
if (hasIgnoreStatusBarIconColor) {
alpha = if (color.isWhite) 0.95f else 0.8f
setColorFilter(if (color.isWhite) color else Color.BLACK)
@@ -700,7 +693,7 @@ class SystemUIHooker : YukiBaseHooker() {
}
}
}
/** 记录实例 */
/** 注册广播 */
injectMember {
method {
name = "setNotification"
@@ -708,22 +701,29 @@ class SystemUIHooker : YukiBaseHooker() {
}.remedys {
method {
name = "setNotification"
param(ExpandedNotificationClass.clazz)
param(ExpandedNotificationClass)
}
}
afterHook {
if (firstArgs != null) instance<ImageView>().also {
registerReceiver(it.context)
statusBarIconViews.add(it)
}
/** 注册壁纸颜色监听 */
if (args().first().any() != null) instance<ImageView>().also { registerWallpaperColorChanged(it) }
}
}
}
/** 注入通知控制器实例 */
StatusBarNotificationPresenterClass.hook {
injectMember {
allConstructors()
afterHook { notificationPresenter = instance }
}
}
/** 注入状态栏通知图标容器实例 */
NotificationIconContainerClass.hook {
injectMember {
method { name = "calculateIconTranslations" }
afterHook {
/** 缓存实例 */
notificationIconContainer = instance<ViewGroup>()
/** 修复部分开发版状态栏图标只能显示一个的问题 */
when (miuiIncrementalVersion.lowercase()) {
"22.3.14", "22.3.15", "22.3.16", "v13.0.1.1.16.dev", "22.3.18" ->
@@ -740,14 +740,14 @@ class SystemUIHooker : YukiBaseHooker() {
.get(instance).set(prefs.get(DataConst.HOOK_STATUS_ICON_COUNT)
.let { if (it in 0..100) it else 5 })
}
}
}.by { NotificationIconContainerClass.clazz.hasField { name = "MAX_STATIC_ICONS" } }
/** 旧版方法 - 新版不存在 */
injectMember {
method {
name = "setMaxStaticIcons"
param(IntType)
}
beforeHook { isShowNotificationIcons = (firstArgs<Int>() ?: 0) > 0 }
beforeHook { isShowNotificationIcons = args().first().int() > 0 }
}.ignoredNoSuchMemberFailure()
/** 新版方法 - 旧版不存在 */
injectMember {
@@ -755,9 +755,9 @@ class SystemUIHooker : YukiBaseHooker() {
name = "miuiShowNotificationIcons"
param(BooleanType)
}
beforeHook { isShowNotificationIcons = firstArgs<Boolean>() ?: false }
beforeHook { isShowNotificationIcons = args().first().boolean() }
}.ignoredNoSuchMemberFailure()
}.by { NotificationIconContainerClass.clazz.hasField { name = "MAX_STATIC_ICONS" } }
}
/** 注入原生通知包装纸实例 */
NotificationHeaderViewWrapperClass.hook {
/** 修复下拉通知图标自动设置回 APP 图标的方法 */
@@ -769,29 +769,27 @@ class SystemUIHooker : YukiBaseHooker() {
/** 获取小图标 */
val iconImageView =
NotificationHeaderViewWrapperClass.clazz
.field { name = "mIcon" }.of<ImageView>(instance) ?: return@afterHook
.field { name = "mIcon" }.get(instance).cast<ImageView>() ?: return@afterHook
/** 获取 [ExpandableNotificationRowClass] */
val rowPair = instance.getRowPair()
/** 获取 [StatusBarNotification] */
val sbnPair = instance.getSbnPair()
val expandedNf = rowPair.second.getSbn()
/** 通知是否展开 */
var isExpanded = sbnPair.first
var isExpanded = rowPair.first
/** 获取优先级 */
val importance =
(iconImageView.context.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager?)
?.getNotificationChannel(sbnPair.second?.notification?.channelId)?.importance ?: 0
?.getNotificationChannel(expandedNf?.notification?.channelId)?.importance ?: 0
/** 非最小化优先级的通知全部设置为展开状态 */
if (importance != 1) isExpanded = true
/** 执行 Hook */
compatNotifyIcon(iconImageView.context, sbnPair.second, iconImageView, isExpanded)
compatNotifyIcon(iconImageView.context, expandedNf, iconImageView, isExpanded)
}
}
/** 记录实例 */
injectMember {
constructor { param(ContextClass, ViewClass, ExpandableNotificationRowClass.clazz) }
afterHook { notificationViewWrappers.add(instance) }
}
}
/** 修改 MIUI 风格通知栏的通知图标 */
MiuiNotificationViewWrapperClass.hook {
@@ -799,15 +797,33 @@ class SystemUIHooker : YukiBaseHooker() {
injectMember {
method { name = "handleAppIcon" }
replaceUnit {
field { name = "mAppIcon" }.of<ImageView>(instance)?.apply {
compatNotifyIcon(context, instance.getSbnPair().second, iconImageView = this, isUseAndroid12Style = true)
field { name = "mAppIcon" }.get(instance).cast<ImageView>()?.apply {
compatNotifyIcon(
context = context,
expandedNf = instance.getRowPair().second.getSbn(),
iconImageView = this,
isUseAndroid12Style = true
)
}
}
}
/** 记录实例 */
}.ignoredHookClassNotFoundFailure()
/** 修改 MIUI 风格通知栏的通知图标 - 折叠通知 */
MiuiNotificationChildrenContainerClass.hook {
/** 替换通知小图标 */
injectMember {
constructor { param(ContextClass, ViewClass, ExpandableNotificationRowClass.clazz) }
afterHook { miuiNotificationViewWrappers.add(instance) }
method {
name = "updateAppIcon"
param(BooleanType)
}
afterHook {
val expandedNf = NotificationChildrenContainerClass.clazz.field {
name = "mContainingNotification"
}.get(instance).self.getSbn()
field { name = "mAppIcon" }.get(instance).cast<ImageView>()?.apply {
compatNotifyIcon(context, expandedNf, iconImageView = this, isUseAndroid12Style = true)
}
}
}
}.ignoredHookClassNotFoundFailure()
/** 干掉下拉通知图标自动设置回 APP 图标的方法 */
@@ -815,11 +831,11 @@ class SystemUIHooker : YukiBaseHooker() {
injectMember {
method {
name = "setAppIcon"
param(ContextClass, ImageViewClass, ExpandedNotificationClass.clazz)
param(ContextClass, ImageViewClass, ExpandedNotificationClass)
}.remedys {
method {
name = "setAppIcon"
param(ImageViewClass, ExpandedNotificationClass.clazz)
param(ImageViewClass, ExpandedNotificationClass)
}
}
intercept()
@@ -827,7 +843,7 @@ class SystemUIHooker : YukiBaseHooker() {
injectMember {
method {
name = "resetIconBgAndPaddings"
param(ImageViewClass, ExpandedNotificationClass.clazz)
param(ImageViewClass, ExpandedNotificationClass)
}
intercept()
}.ignoredNoSuchMemberFailure()
@@ -840,7 +856,7 @@ class SystemUIHooker : YukiBaseHooker() {
param(ContextClass, IntentClass)
}
afterHook {
if (isEnableHookColorNotifyIcon()) (lastArgs as? Intent)?.also {
if (isEnableHookColorNotifyIcon()) args().last().cast<Intent>()?.also {
if (it.action.equals(Intent.ACTION_PACKAGE_REPLACED).not() &&
it.getBooleanExtra(Intent.EXTRA_REPLACING, false)
) return@also
@@ -850,11 +866,11 @@ class SystemUIHooker : YukiBaseHooker() {
if (iconDatas.takeIf { e -> e.isNotEmpty() }
?.filter { e -> e.packageName == newPkgName }
.isNullOrEmpty()
) IconAdaptationTool.pushNewAppSupportNotify(firstArgs()!!, newPkgName)
) IconAdaptationTool.pushNewAppSupportNotify(args().first().cast()!!, newPkgName)
}
Intent.ACTION_PACKAGE_REMOVED ->
IconAdaptationTool.removeNewAppSupportNotify(
context = firstArgs()!!,
context = args().first().cast()!!,
packageName = it.data?.schemeSpecificPart ?: ""
)
}
@@ -867,11 +883,14 @@ class SystemUIHooker : YukiBaseHooker() {
injectMember {
method { name = "updateTime" }
afterHook {
if (isEnableHookColorNotifyIcon() && prefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX_AUTO))
IconAdaptationTool.prepareAutoUpdateIconRule(
context = instance<View>().context,
timeSet = prefs.get(DataConst.NOTIFY_ICON_FIX_AUTO_TIME)
)
instance<View>().context.also {
/** 注册定时监听 */
if (isEnableHookColorNotifyIcon() && prefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX_AUTO))
IconAdaptationTool.prepareAutoUpdateIconRule(
context = it,
timeSet = prefs.get(DataConst.NOTIFY_ICON_FIX_AUTO_TIME)
)
}
}
}
}.ignoredHookClassNotFoundFailure()

View File

@@ -43,7 +43,7 @@ import com.fankes.miui.notify.ui.activity.base.BaseActivity
import com.fankes.miui.notify.utils.factory.*
import com.fankes.miui.notify.utils.tool.IconRuleManagerTool
import com.fankes.miui.notify.utils.tool.SystemUITool
import com.highcapable.yukihookapi.hook.factory.isXposedModuleActive
import com.highcapable.yukihookapi.YukiHookAPI
class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
@@ -61,7 +61,7 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
override fun onCreate() {
/** 检查激活状态 */
if (isXposedModuleActive.not()) {
if (YukiHookAPI.Status.isXposedModuleActive.not()) {
showDialog {
title = "模块没有激活"
msg = "模块没有激活,你无法使用这里的功能,请先激活模块。"
@@ -154,8 +154,19 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
holder.adpAppAllSwitch.isChecked = isAppNotifyHookAllOf(it)
holder.adpAppAllSwitch.setOnCheckedChangeListener { btn, b ->
if (btn.isPressed.not()) return@setOnCheckedChangeListener
putAppNotifyHookAllOf(it, b)
SystemUITool.refreshSystemUI(context = this@ConfigureActivity)
fun saveState() {
putAppNotifyHookAllOf(it, 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!!
@@ -176,7 +187,13 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
}
/** 设置点击事件 */
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()

View File

@@ -28,8 +28,8 @@ import android.content.ComponentName
import android.content.pm.PackageManager
import androidx.core.view.isGone
import androidx.core.view.isVisible
import com.fankes.miui.notify.BuildConfig
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.databinding.ActivityMainBinding
import com.fankes.miui.notify.databinding.DiaStatusIconCountBinding
@@ -38,9 +38,9 @@ import com.fankes.miui.notify.ui.activity.base.BaseActivity
import com.fankes.miui.notify.utils.factory.*
import com.fankes.miui.notify.utils.tool.GithubReleaseTool
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.xposed.YukiHookModuleStatus
class MainActivity : BaseActivity<ActivityMainBinding>() {
@@ -50,7 +50,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
internal var isActivityLive = false
/** 模块版本 */
private const val moduleVersion = Const.MODULE_VERSION_NAME
private const val moduleVersion = BuildConfig.VERSION_NAME
/** 预发布的版本标识 */
private const val pendingFlag = ""
@@ -105,7 +105,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
noCancelable()
}
/** 判断是否 Hook */
isXposedModuleActive -> {
YukiHookAPI.Status.isXposedModuleActive -> {
if (IconPackParams(context = this).iconDatas.isEmpty() && modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX))
showDialog {
title = "配置通知图标优化名单"
@@ -123,6 +123,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
cancelButton()
noCancelable()
}
/** 推广、恰饭 */
YukiPromoteTool.promote(context = this)
}
else ->
showDialog {
@@ -141,6 +143,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
binding.statusIconCountItem.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.notifyIconForceAppIconItem.isVisible = modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX)
binding.notifyIconFixNotifyItem.isVisible = modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX)
binding.notifyIconAutoSyncItem.isVisible = modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX)
binding.statusIconCountSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_HOOK_STATUS_ICON_COUNT)
@@ -151,6 +154,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
binding.hideIconInLauncherSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_HIDE_ICON)
binding.colorIconCompatSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_COLOR_ICON_COMPAT)
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.notifyIconAutoSyncSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_NOTIFY_ICON_FIX_AUTO)
binding.statusIconCountText.text = statusBarIconCount.toString()
@@ -193,10 +197,27 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
if (btn.isPressed.not()) return@setOnCheckedChangeListener
modulePrefs.put(DataConst.ENABLE_NOTIFY_ICON_FIX, b)
binding.notifyIconFixButton.isVisible = b
binding.notifyIconForceAppIconItem.isVisible = b
binding.notifyIconFixNotifyItem.isVisible = b
binding.notifyIconAutoSyncItem.isVisible = b
SystemUITool.refreshSystemUI(context = this)
}
binding.notifyIconForceAppIconSwitch.setOnCheckedChangeListener { btn, b ->
if (btn.isPressed.not()) return@setOnCheckedChangeListener
fun saveState() {
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 ->
if (btn.isPressed.not()) return@setOnCheckedChangeListener
modulePrefs.put(DataConst.ENABLE_NOTIFY_ICON_FIX_NOTIFY, b)
@@ -274,36 +295,36 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
private fun refreshModuleStatus() {
binding.mainLinStatus.setBackgroundResource(
when {
isXposedModuleActive && (isModuleRegular.not() || isModuleValied.not()) -> R.drawable.bg_yellow_round
isXposedModuleActive -> R.drawable.bg_green_round
YukiHookAPI.Status.isXposedModuleActive && (isModuleRegular.not() || isModuleValied.not()) -> R.drawable.bg_yellow_round
YukiHookAPI.Status.isXposedModuleActive -> R.drawable.bg_green_round
else -> R.drawable.bg_dark_round
}
)
binding.mainImgStatus.setImageResource(
when {
isXposedModuleActive -> R.mipmap.ic_success
YukiHookAPI.Status.isXposedModuleActive -> R.mipmap.ic_success
else -> R.mipmap.ic_warn
}
)
binding.mainTextStatus.text =
when {
isXposedModuleActive && isModuleRegular.not() && modulePrefs.get(DataConst.ENABLE_MODULE).not() -> "模块已停用"
isXposedModuleActive && isModuleRegular.not() -> "模块已激活,请重启系统界面"
isXposedModuleActive && isModuleValied.not() -> "模块已更新,请重启系统界面"
isXposedModuleActive -> "模块已激活"
YukiHookAPI.Status.isXposedModuleActive && isModuleRegular.not() && modulePrefs.get(DataConst.ENABLE_MODULE).not() -> "模块已停用"
YukiHookAPI.Status.isXposedModuleActive && isModuleRegular.not() -> "模块已激活,请重启系统界面"
YukiHookAPI.Status.isXposedModuleActive && isModuleValied.not() -> "模块已更新,请重启系统界面"
YukiHookAPI.Status.isXposedModuleActive -> "模块已激活"
else -> "模块未激活"
}
binding.mainTextApiWay.isVisible = isXposedModuleActive
binding.mainTextApiWay.text = "Activated by ${YukiHookModuleStatus.executorName} API ${YukiHookModuleStatus.executorVersion}"
binding.mainTextApiWay.isVisible = YukiHookAPI.Status.isXposedModuleActive
binding.mainTextApiWay.text = "Activated by ${YukiHookAPI.Status.executorName} API ${YukiHookAPI.Status.executorVersion}"
}
override fun onResume() {
super.onResume()
/** 刷新模块状态 */
refreshModuleStatus()
/** 发送广播检查模块激活状态 */
SystemUITool.checkingActivated(context = this) { isRegular, isValied ->
isModuleRegular = isRegular
/** 检查模块激活状态 */
SystemUITool.checkingActivated(context = this) { isValied ->
isModuleRegular = true
isModuleValied = isValied
refreshModuleStatus()
}

View File

@@ -28,11 +28,11 @@ import android.app.Activity
import android.os.Bundle
import android.view.View
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.utils.factory.delayedRun
import com.fankes.miui.notify.utils.tool.IconRuleManagerTool
import com.fankes.miui.notify.utils.tool.SystemUITool
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication.Companion.appContext
class NotifyIconRuleUpdateActivity : Activity() {
@@ -48,8 +48,6 @@ class NotifyIconRuleUpdateActivity : Activity() {
finish()
return
}
/** 注册 */
SystemUITool.register(context = this)
/** 拉取云端数据 */
IconRuleManagerTool.sync(appContext) {
/** 刷新系统界面 */
@@ -60,10 +58,4 @@ class NotifyIconRuleUpdateActivity : Activity() {
/** 切换到后台 */
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 androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.ViewCompat
import androidx.viewbinding.ViewBinding
import com.fankes.miui.notify.R
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.type.android.LayoutInflaterClass
import java.lang.reflect.ParameterizedType
@@ -61,26 +61,19 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
/** 隐藏系统的标题栏 */
supportActionBar?.hide()
/** 初始化沉浸状态栏 */
immersionBar {
statusBarColor(R.color.colorThemeBackground)
autoDarkModeEnable(true)
statusBarDarkFont(isNotSystemInDarkMode)
navigationBarColor(R.color.colorThemeBackground)
navigationBarDarkIcon(isNotSystemInDarkMode)
fitsSystemWindows(true)
ViewCompat.getWindowInsetsController(window.decorView)?.apply {
isAppearanceLightStatusBars = isNotSystemInDarkMode
isAppearanceLightNavigationBars = isNotSystemInDarkMode
}
ResourcesCompat.getColor(resources, R.color.colorThemeBackground, null).also {
window?.statusBarColor = it
window?.navigationBarColor = it
window?.navigationBarDividerColor = it
}
/** 注册 */
SystemUITool.register(context = this)
/** 装载子类 */
onCreate()
}
/** 回调 [onCreate] 方法 */
abstract fun onCreate()
override fun onDestroy() {
super.onDestroy()
/** 解除注册 */
SystemUITool.unregister(context = this)
}
}

View File

@@ -45,7 +45,7 @@ class MaterialSwitch(context: Context, attrs: AttributeSet?) : SwitchCompat(cont
return ColorStateList(states, colors)
}
private val thumbColor get() = if (isSystemInDarkMode) 0xFF7C7C7C else 0xFFCCCCCC
private val thumbColor get() = if (context.isSystemInDarkMode) 0xFF7C7C7C else 0xFFCCCCCC
init {
trackDrawable = DrawableBuilder()

View File

@@ -39,25 +39,25 @@ import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.viewbinding.ViewBinding
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.highcapable.yukihookapi.annotation.DoNotUseField
import com.highcapable.yukihookapi.annotation.CauseProblemsApi
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
/**
* 构造对话框
* @param isUseBlackTheme 是否使用深色主题
* @param it 对话框方法体
* @param initiate 对话框方法体
*/
fun Context.showDialog(isUseBlackTheme: Boolean = false, it: DialogBuilder.() -> Unit) =
DialogBuilder(this, isUseBlackTheme).apply(it).show()
fun Context.showDialog(isUseBlackTheme: Boolean = false, initiate: DialogBuilder.() -> Unit) =
DialogBuilder(context = this, isUseBlackTheme).apply(initiate).show()
/**
* 显示时间选择对话框
* @param timeSet 当前时间 - 不写将使用当前时间格式HH:mm
* @param it 回调 - 小时与分钟 HH:mm
* @param result 回调 - 小时与分钟 HH:mm
*/
fun Context.showTimePicker(timeSet: String = "", it: (String) -> Unit) =
TimePickerDialog(this, { _, h, m -> it("${h.autoZero}:${m.autoZero}") }, timeSet.hour, timeSet.minute, true).show()
fun Context.showTimePicker(timeSet: String = "", result: (String) -> Unit) =
TimePickerDialog(this, { _, h, m -> result("${h.autoZero}:${m.autoZero}") }, timeSet.hour, timeSet.minute, true).show()
/**
* 对话框构造器
@@ -71,7 +71,7 @@ class DialogBuilder(val context: Context, private val isUseBlackTheme: Boolean)
private var dialogInstance: Dialog? = null // 对话框实例
@DoNotUseField
@CauseProblemsApi
var customLayoutView: View? = null // 自定义布局
/**
@@ -150,34 +150,34 @@ class DialogBuilder(val context: Context, private val isUseBlackTheme: Boolean)
/**
* 设置对话框确定按钮
* @param text 按钮文本内容
* @param it 点击事件
* @param callback 点击事件
*/
fun confirmButton(text: String = "确定", it: () -> Unit = {}) {
fun confirmButton(text: String = "确定", callback: () -> Unit = {}) {
if (isUsingAndroidX)
runInSafe { instanceAndroidX?.setPositiveButton(text) { _, _ -> it() } }
else runInSafe { instanceAndroid?.setPositiveButton(text) { _, _ -> it() } }
runInSafe { instanceAndroidX?.setPositiveButton(text) { _, _ -> callback() } }
else runInSafe { instanceAndroid?.setPositiveButton(text) { _, _ -> callback() } }
}
/**
* 设置对话框取消按钮
* @param text 按钮文本内容
* @param it 点击事件
* @param callback 点击事件
*/
fun cancelButton(text: String = "取消", it: () -> Unit = {}) {
fun cancelButton(text: String = "取消", callback: () -> Unit = {}) {
if (isUsingAndroidX)
runInSafe { instanceAndroidX?.setNegativeButton(text) { _, _ -> it() } }
else runInSafe { instanceAndroid?.setNegativeButton(text) { _, _ -> it() } }
runInSafe { instanceAndroidX?.setNegativeButton(text) { _, _ -> callback() } }
else runInSafe { instanceAndroid?.setNegativeButton(text) { _, _ -> callback() } }
}
/**
* 设置对话框第三个按钮
* @param text 按钮文本内容
* @param it 点击事件
* @param callback 点击事件
*/
fun neutralButton(text: String = "更多", it: () -> Unit = {}) {
fun neutralButton(text: String = "更多", callback: () -> Unit = {}) {
if (isUsingAndroidX)
runInSafe { instanceAndroidX?.setNeutralButton(text) { _, _ -> it() } }
else runInSafe { instanceAndroid?.setNeutralButton(text) { _, _ -> it() } }
runInSafe { instanceAndroidX?.setNeutralButton(text) { _, _ -> callback() } }
else runInSafe { instanceAndroid?.setNeutralButton(text) { _, _ -> callback() } }
}
/** 取消对话框 */

View File

@@ -32,6 +32,7 @@ import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.res.Configuration
@@ -46,17 +47,18 @@ import android.util.Base64
import android.widget.Toast
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.getSystemService
import com.fankes.miui.notify.application.MNNApplication.Companion.appContext
import com.google.android.material.snackbar.Snackbar
import com.highcapable.yukihookapi.hook.factory.classOf
import com.highcapable.yukihookapi.hook.factory.hasClass
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.type.java.StringType
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication.Companion.appContext
import com.topjohnwu.superuser.Shell
import java.io.ByteArrayOutputStream
import java.text.SimpleDateFormat
import java.util.*
/**
* 系统深色模式是否开启
* @return [Boolean] 是否开启
@@ -237,6 +239,14 @@ fun Context.findAppName(name: String) =
fun Context.findAppIcon(name: String) =
safeOfNull { packageManager?.getPackageInfo(name, 0)?.applicationInfo?.loadIcon(packageManager) }
/**
* 获取 APP 是否为 DEBUG 版本
* @param name APP 包名
* @return [Boolean]
*/
fun Context.isAppDebuggable(name: String) =
safeOfFalse { (packageManager?.getPackageInfo(name, 0)?.applicationInfo?.flags?.and(ApplicationInfo.FLAG_DEBUGGABLE) ?: 0) != 0 }
/**
* 对数值自动补零
* @return [String]
@@ -305,7 +315,20 @@ val Context.systemAccentColor
*/
val Context.wallpaperColor
get() = safeOfNan {
WallpaperManager.getInstance(this).getWallpaperColors(WallpaperManager.FLAG_SYSTEM)?.secondaryColor?.toArgb() ?: 0
WallpaperManager.getInstance(this).getWallpaperColors(WallpaperManager.FLAG_SYSTEM)?.primaryColor?.toArgb() ?: 0
}
/**
* 获取较浅的颜色
* @return [Int]
*/
val Int.brighter: Int
get() {
val hsv = FloatArray(3)
Color.colorToHSV(this, hsv)
hsv[1] = hsv[1] - 0.3f
hsv[2] = hsv[2] + 0.3f
return Color.HSVToColor(hsv)
}
/**
@@ -389,12 +412,19 @@ fun findPropString(key: String, default: String = "") = safeOf(default) {
}
/**
* 执行命令 - su
* 是否有 Root 权限
* @return [Boolean]
*/
val isRootAccess get() = safeOfFalse { Shell.rootAccess() }
/**
* 执行命令
* @param cmd 命令
* @param isSu 是否使用 Root 权限执行 - 默认:是
* @return [String] 执行结果
*/
fun execShellSu(cmd: String) = safeOfNothing {
Shell.su(cmd).exec().out.let {
fun execShell(cmd: String, isSu: Boolean = true) = safeOfNothing {
(if (isSu) Shell.su(cmd) else Shell.sh(cmd)).exec().out.let {
if (it.isNotEmpty()) it[0].trim() else ""
}
}
@@ -410,28 +440,24 @@ fun toast(msg: String) = Toast.makeText(appContext, msg, Toast.LENGTH_SHORT).sho
*
* [T] 为指定的 [Activity]
*/
inline fun <reified T : Activity> Context.navigate() = runInSafe { startActivity(Intent(this, T::class.java)) }
/**
* 跳转到指定页面
*
* [T] 为指定的 [Activity]
*/
inline fun <reified T : Activity> Service.navigate() =
runInSafe { startActivity(Intent(this, T::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }) }
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
})
}
/**
* 弹出 [Snackbar]
* @param msg 提示内容
* @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)
.apply {
if (actionText.isBlank()) return@apply
setActionTextColor(if (isSystemInDarkMode) Color.BLACK else Color.WHITE)
setAction(actionText) { it() }
setAction(actionText) { callback() }
}.show()
/**

View File

@@ -45,9 +45,9 @@ object GithubReleaseTool {
* 获取最新版本信息
* @param context 实例
* @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(
Request.Builder()
.url("https://api.github.com/repos/$REPO_AUTHOR/$REPO_NAME/releases/latest")
@@ -73,7 +73,7 @@ object GithubReleaseTool {
}
if (name != version) (context as? Activity?)?.runOnUiThread {
showUpdate()
it(name) { showUpdate() }
result(name) { showUpdate() }
}
}
}
@@ -84,9 +84,9 @@ object GithubReleaseTool {
/**
* 检查网络连接情况
* @param context 实例
* @param it 已连接回调
* @param callback 已连接回调
*/
private fun checkingInternetConnect(context: Context, it: () -> Unit) = runInSafe {
private fun checkingInternetConnect(context: Context, callback: () -> Unit) = runInSafe {
if (isNetWorkSuccess)
OkHttpClient().newBuilder().build().newCall(
Request.Builder()
@@ -107,7 +107,7 @@ object GithubReleaseTool {
}
override fun onResponse(call: Call, response: Response) = runInSafe {
(context as? Activity?)?.runOnUiThread { runInSafe { it() } }
(context as? Activity?)?.runOnUiThread { runInSafe { callback() } }
}
})
}

View File

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

View File

@@ -75,9 +75,9 @@ object IconRuleManagerTool {
/**
* 从在线地址手动同步规则
* @param context 实例
* @param it 成功后回调
* @param callback 成功后回调
*/
fun syncByHand(context: Context, it: () -> Unit) =
fun syncByHand(context: Context, callback: () -> Unit) =
context.showDialog {
title = "同步列表"
var sourceType = context.modulePrefs.get(DataConst.SOURCE_SYNC_WAY)
@@ -116,12 +116,12 @@ object IconRuleManagerTool {
confirmButton {
context.modulePrefs.put(DataConst.SOURCE_SYNC_WAY, sourceType)
context.modulePrefs.put(DataConst.SOURCE_SYNC_WAY_CUSTOM_URL, customUrl)
sync(context, sourceType, customUrl, it)
sync(context, sourceType, customUrl, callback)
}
cancelButton()
neutralButton(text = "自定义规则") {
context.showDialog {
title = "自定义规则"
title = "自定义规则(调试)"
val editText = bind<DiaSourceFromStringBinding>().diaSfsInputEdit.apply {
requestFocus()
invalidate()
@@ -139,7 +139,7 @@ object IconRuleManagerTool {
)
)
notifyRefresh(context)
it()
callback()
}
else -> context.snake(msg = "请输入有效内容")
}
@@ -152,7 +152,7 @@ object IconRuleManagerTool {
jsonString.isNotBlank() -> {
params.save(dataJson = jsonString.takeIf { params.isJsonArray(it) } ?: "[$jsonString]")
notifyRefresh(context)
it()
callback()
}
else -> context.snake(msg = "请输入有效内容")
}
@@ -169,23 +169,23 @@ object IconRuleManagerTool {
* @param context 实例
* @param sourceType 同步地址类型 - 默认自动获取已存储的键值
* @param customUrl 自定义同步地址 - 默认自动获取已存储的键值
* @param it 成功后回调
* @param callback 成功后回调
*/
fun sync(
context: Context,
sourceType: Int = context.modulePrefs.get(DataConst.SOURCE_SYNC_WAY),
customUrl: String = context.modulePrefs.get(DataConst.SOURCE_SYNC_WAY_CUSTOM_URL),
it: () -> Unit
callback: () -> Unit
) {
when (sourceType) {
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 ->
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 ->
if (customUrl.isNotBlank())
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 = "同步地址不能为空")
else -> context.snakeOrNotify(title = "同步异常", msg = "同步类型错误")
@@ -196,14 +196,14 @@ object IconRuleManagerTool {
* 开始更新数据
* @param context 实例
* @param url
* @param it 成功后回调
* @param callback 成功后回调
*/
private fun onRefreshing(context: Context, url: String, it: () -> Unit) = checkingInternetConnect(context) {
fun doParse(callback: (Boolean) -> Unit = {}) {
private fun onRefreshing(context: Context, url: String, callback: () -> Unit) = checkingInternetConnect(context) {
fun doParse(result: (Boolean) -> Unit = {}) {
wait(context, url = "$url/OS/$OS_TAG/NotifyIconsSupportConfig.json") { isDone1, ctOS ->
callback(true)
result(true)
wait(context, url = "$url/APP/NotifyIconsSupportConfig.json") { isDone2, ctAPP ->
callback(false)
result(false)
IconPackParams(context).also { params ->
when {
isDone1 && isDone2 -> params.splicingJsonArray(ctOS, ctAPP).also {
@@ -216,7 +216,7 @@ object IconRuleManagerTool {
params.save(it)
pushNotify(context, title = "同步完成", msg = "已更新通知图标优化名单,点击查看")
notifyRefresh(context)
it()
callback()
}
else -> (if (context is AppCompatActivity) context.snake(msg = "列表数据已是最新"))
}
@@ -225,7 +225,7 @@ object IconRuleManagerTool {
context.showDialog {
title = "连接失败"
msg = "连接失败,错误如下:\n${if (isDone1.not()) ctOS else ctAPP}"
confirmButton(text = "再试一次") { syncByHand(context, it) }
confirmButton(text = "再试一次") { syncByHand(context, callback) }
cancelButton()
}
else -> pushNotify(context, title = "同步地址不可用", msg = if (isDone1.not()) ctOS else ctAPP)
@@ -248,12 +248,12 @@ object IconRuleManagerTool {
* 开始更新数据
* @param context 实例
* @param url
* @param it 成功后回调
* @param callback 成功后回调
*/
private fun onRefreshingCustom(context: Context, url: String, it: () -> Unit) = checkingInternetConnect(context) {
fun doParse(callback: () -> Unit = {}) {
private fun onRefreshingCustom(context: Context, url: String, callback: () -> Unit) = checkingInternetConnect(context) {
fun doParse(result: () -> Unit = {}) {
wait(context, url) { isDone, content ->
callback()
result()
IconPackParams(context).also { params ->
when {
isDone -> when {
@@ -265,7 +265,7 @@ object IconRuleManagerTool {
params.save(content)
pushNotify(context, title = "同步完成", msg = "已更新通知图标优化名单,点击查看")
notifyRefresh(context)
it()
callback()
}
else -> (if (context is AppCompatActivity) context.snake(msg = "列表数据已是最新"))
}
@@ -293,16 +293,16 @@ object IconRuleManagerTool {
/**
* 检查网络连接情况
* @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 {
title = "准备中"
progressContent = "正在检查网络连接情况"
noCancelable()
baseCheckingInternetConnect(context) { isDone ->
cancel()
if (isDone) it() else
if (isDone) callback() else
context.showDialog {
title = "网络不可用"
msg = "无法连接到互联网,请检查你当前的设备是否可以上网,且没有在手机管家中禁用本模块的联网权限。"
@@ -319,24 +319,24 @@ object IconRuleManagerTool {
}
}
} else baseCheckingInternetConnect(context) { isDone ->
if (isDone) it() else pushNotify(context, title = "网络不可用", msg = "无法连接到互联网,无法更新通知图标规则")
if (isDone) callback() else pushNotify(context, title = "网络不可用", msg = "无法连接到互联网,无法更新通知图标规则")
}
/**
* 检查网络连接情况
* @param context 实例
* @param it 已连接回调
* @param result 已连接回调
*/
private fun baseCheckingInternetConnect(context: Context, it: (Boolean) -> Unit) =
wait(context, url = "https://www.baidu.com") { isDone, _ -> it(isDone) }
private fun baseCheckingInternetConnect(context: Context, result: (Boolean) -> Unit) =
wait(context, url = "https://www.baidu.com") { isDone, _ -> result(isDone) }
/**
* 发送 GET 请求内容并等待
* @param context 实例
* @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 {
SSLSocketClient.sSLSocketFactory?.let { sslSocketFactory(it, SSLSocketClient.trustManager) }
hostnameVerifier(SSLSocketClient.hostnameVerifier)
@@ -347,15 +347,15 @@ object IconRuleManagerTool {
.build()
).enqueue(object : Callback {
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) {
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
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
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.hook.HookConst.SYSTEMUI_PACKAGE_NAME
import com.fankes.miui.notify.utils.factory.*
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 {
/** 宿主广播回调 */
private var moduleHandlerCallback: ((Boolean, Boolean) -> Unit)? = null
/** 通知广播回调 */
private var remindHandlerCallback: ((Boolean, Boolean) -> Unit)? = null
private val CALL_HOST_REFRESH_CACHING = ChannelData("call_host_refresh_caching", false)
private val CALL_MODULE_REFRESH_RESULT = ChannelData("call_module_refresh_result", false)
/**
* 注册广播
* @param context 实例
* 宿主注册监听
*/
fun register(context: Context) {
/** 注册广播检查模块激活状态 */
context.registerReceiver(moduleHandlerReceiver, IntentFilter().apply { addAction(Const.ACTION_MODULE_HANDLER_RECEIVER) })
/** 注册广播通知系统界面改变 */
context.registerReceiver(remindHandlerReceiver, IntentFilter().apply { addAction(Const.ACTION_REMIND_HANDLER_RECEIVER) })
}
object Host {
/**
* 取消注册广播
* @param context 实例
*/
fun unregister(context: Context) {
context.unregisterReceiver(moduleHandlerReceiver)
context.unregisterReceiver(remindHandlerReceiver)
/**
* 监听系统界面刷新改变
* @param param 实例
* @param result 回调 - ([Boolean] 是否成功)
*/
fun onRefreshSystemUI(param: PackageParam, result: (Boolean) -> Boolean) {
param.dataChannel.with { wait(CALL_HOST_REFRESH_CACHING) { put(CALL_MODULE_REFRESH_RESULT, result(it)) } }
}
}
/**
* 检查模块是否激活
* @param context 实例
* @param it 成功后回调 - ([Boolean] 是否激活,[Boolean] 是否有效)
* @param result 成功后回调
*/
fun checkingActivated(context: Context, it: (Boolean, Boolean) -> Unit) {
moduleHandlerCallback = it
context.sendBroadcast(Intent().apply {
action = Const.ACTION_MODULE_CHECKING_RECEIVER
putExtra(Const.MODULE_VERSION_VERIFY_TAG, Const.MODULE_VERSION_VERIFY)
})
}
fun checkingActivated(context: Context, result: (Boolean) -> Unit) = context.dataChannel(SYSTEMUI_PACKAGE_NAME).checkingVersionEquals(result)
/**
* 重启系统界面
@@ -87,9 +72,9 @@ object SystemUITool {
"部分 MIUI 内测和开发版中使用了状态栏主题可能会发生主题失效的情况,这种情况请再重启一次即可。\n\n" +
"重启过程会黑屏并等待进入锁屏重新解锁。"
confirmButton {
execShellSu(cmd = "pgrep systemui").also { pid ->
execShell(cmd = "pgrep systemui").also { pid ->
if (pid.isNotBlank())
execShellSu(cmd = "kill -9 $pid")
execShell(cmd = "kill -9 $pid")
else toast(msg = "ROOT 权限获取失败")
}
}
@@ -100,17 +85,10 @@ object SystemUITool {
* 刷新系统界面状态栏与通知图标
* @param context 实例
* @param isRefreshCacheOnly 仅刷新缓存不刷新图标和通知改变 - 默认:否
* @param it 成功后回调
* @param callback 成功后回调
*/
fun refreshSystemUI(context: Context? = null, isRefreshCacheOnly: Boolean = false, it: () -> Unit = {}) = runInSafe {
fun sendMessage() {
(context ?: appContext).sendBroadcast(Intent().apply {
action = Const.ACTION_REMIND_CHECKING_RECEIVER
putExtra("isRefreshCacheOnly", isRefreshCacheOnly)
putExtra(Const.MODULE_VERSION_VERIFY_TAG, Const.MODULE_VERSION_VERIFY)
})
}
if (isXposedModuleActive)
fun refreshSystemUI(context: Context? = null, isRefreshCacheOnly: Boolean = false, callback: () -> Unit = {}) = runInSafe {
if (YukiHookAPI.Status.isXposedModuleActive)
context?.showDialog {
title = "请稍后"
progressContent = "正在等待系统界面刷新"
@@ -119,23 +97,29 @@ object SystemUITool {
/** 设置等待延迟 */
delayedRun(ms = 5000) {
if (isWaited) return@delayedRun
remindHandlerCallback = null
cancel()
context.snake(msg = "预计响应超时,建议重启系统界面", actionText = "立即重启") { restartSystemUI(context) }
}
remindHandlerCallback = { isGrasp, isValied ->
remindHandlerCallback = null
cancel()
isWaited = true
checkingActivated(context) { isValied ->
when {
isGrasp && !isValied ->
isValied.not() -> {
cancel()
isWaited = true
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()
} ?: sendMessage()
}
else context?.snake(msg = "模块没有激活,更改不会生效")
}
@@ -144,29 +128,7 @@ object SystemUITool {
* @param context 实例
*/
fun showNeedRestartSnake(context: Context) =
if (isXposedModuleActive)
if (YukiHookAPI.Status.isXposedModuleActive)
context.snake(msg = "设置需要重启系统界面才能生效", actionText = "立即重启") { restartSystemUI(context) }
else context.snake(msg = "模块没有激活,更改不会生效")
/** 宿主广播接收器 */
private val moduleHandlerReceiver by lazy {
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val isRegular = intent?.getBooleanExtra("isRegular", false) ?: false
val isValied = intent?.getBooleanExtra("isValied", false) ?: false
moduleHandlerCallback?.invoke(isRegular, isValied)
}
}
}
/** 通知广播接收器 */
private val remindHandlerReceiver by lazy {
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val isGrasp = intent?.getBooleanExtra("isGrasp", false) ?: false
val isValied = intent?.getBooleanExtra("isValied", false) ?: false
remindHandlerCallback?.invoke(isGrasp, isValied)
}
}
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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.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", 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

@@ -541,6 +541,34 @@
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<LinearLayout
android:id="@+id/notify_icon_force_app_icon_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
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
android:id="@+id/notify_icon_fix_notify_item"
android:layout_width="match_parent"

View File

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

View File

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

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
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
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME