mirror of
https://github.com/fankes/MIUINativeNotifyIcon.git
synced 2025-09-06 02:35:32 +08:00
Compare commits
57 Commits
Author | SHA1 | Date | |
---|---|---|---|
107569607d
|
|||
5d099d2ec6
|
|||
45da218cc8
|
|||
9a8cc9378c
|
|||
d7620f62b1
|
|||
cf9f81be23
|
|||
2e1929f36e
|
|||
cb6cbf0398
|
|||
dbdd0fc514
|
|||
21f4ff9ad3
|
|||
1bc79056a9
|
|||
41cb741dff
|
|||
6578027a34
|
|||
01cb647f0c
|
|||
171cd6a6d1
|
|||
02f8852ab0
|
|||
826805c552 | |||
627697d586 | |||
f5615ff89f | |||
dd45e57b00 | |||
d73239de73 | |||
deb0ca794f | |||
84b3648626 | |||
ae8fb6514c | |||
197f427adb | |||
58a23b4537 | |||
72bbdb69a8 | |||
679ae26462 | |||
eefa7ee9e7 | |||
217d5d9bc3 | |||
4d1e383ef5 | |||
25399cd88b | |||
18691d8b8d | |||
7c61b09054 | |||
7a7e179a2b | |||
dbd7dbf172 | |||
502e5dc04b | |||
ff0c57319a | |||
647a39c327 | |||
04e8d43356 | |||
f85d2db36e | |||
2e5284ebe9 | |||
abffc4d853 | |||
5d81b8ff87 | |||
d91d2736e6 | |||
72169989f2 | |||
c85da002f5 | |||
c09a1b5760 | |||
c30e56c5aa | |||
822e988c16 | |||
fc728a75a4 | |||
256e2ebfce | |||
be4b952f62 | |||
c7263c6d94 | |||
4733578912 | |||
87879301f0 | |||
c9be72e5f7 |
359
PRIVACY.md
Normal file
359
PRIVACY.md
Normal 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.
|
80
README.md
80
README.md
@@ -2,52 +2,61 @@
|
||||
|
||||
[](https://github.com/fankes/MIUINativeNotifyIcon)
|
||||
[](https://github.com/fankes/MIUINativeNotifyIcon/blob/master/LICENSE)
|
||||
[](https://github.com/fankes/MIUINativeNotifyIcon/releases)
|
||||
[](https://github.com/fankes/MIUINativeNotifyIcon/releases)
|
||||
[](https://github.com/fankes/MIUINativeNotifyIcon/releases)
|
||||
[](https://github.com/Xposed-Modules-Repo/com.fankes.miui.notify/releases)
|
||||
[](https://t.me/XiaofangInternet)
|
||||
[](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)
|
@@ -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'
|
||||
|
8
app/proguard-rules.pro
vendored
8
app/proguard-rules.pro
vendored
@@ -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(...);
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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"
|
||||
}
|
@@ -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)
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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()
|
||||
|
@@ -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()
|
||||
|
@@ -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()
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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()
|
||||
|
@@ -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() } }
|
||||
}
|
||||
|
||||
/** 取消对话框 */
|
||||
|
@@ -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()
|
||||
|
||||
/**
|
||||
|
@@ -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() } }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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 无效") }
|
||||
|
||||
/**
|
||||
* 推送通知图标更新通知
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@@ -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"
|
||||
|
@@ -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">
|
||||
|
11
build.gradle
11
build.gradle
@@ -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) {
|
||||
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -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
|
||||
|
Reference in New Issue
Block a user