9 Commits
1.53 ... 1.6

8 changed files with 79 additions and 44 deletions

View File

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

View File

@@ -38,7 +38,7 @@ android {
buildTypes { buildTypes {
release { release {
minifyEnabled true minifyEnabled rootProject.ext.enableR8
signingConfig signingConfigs.debug signingConfig signingConfigs.debug
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
@@ -72,8 +72,8 @@ tasks.whenTaskAdded {
dependencies { dependencies {
compileOnly 'de.robv.android.xposed:api:82' compileOnly 'de.robv.android.xposed:api:82'
implementation 'com.highcapable.yukihookapi:api:1.0.71' implementation 'com.highcapable.yukihookapi:api:1.0.75'
ksp 'com.highcapable.yukihookapi:ksp-xposed:1.0.71' ksp 'com.highcapable.yukihookapi:ksp-xposed:1.0.75'
implementation 'com.github.tiann:FreeReflection:3.1.0' implementation 'com.github.tiann:FreeReflection:3.1.0'
implementation "com.github.topjohnwu.libsu:core:3.1.2" implementation "com.github.topjohnwu.libsu:core:3.1.2"
implementation 'androidx.annotation:annotation:1.3.0' implementation 'androidx.annotation:annotation:1.3.0'

View File

@@ -41,7 +41,7 @@ object Const {
const val MODULE_VERSION_CODE = BuildConfig.VERSION_CODE const val MODULE_VERSION_CODE = BuildConfig.VERSION_CODE
/** 当前模块的版本校验 */ /** 当前模块的版本校验 */
const val MODULE_VERSION_VERIFY = "${MODULE_VERSION_NAME}_${MODULE_VERSION_CODE}_202204042319" const val MODULE_VERSION_VERIFY = "${MODULE_VERSION_NAME}_${MODULE_VERSION_CODE}_202204140318"
/** 当前模块的版本校验标签 */ /** 当前模块的版本校验标签 */
const val MODULE_VERSION_VERIFY_TAG = "module_version_verify" const val MODULE_VERSION_VERIFY_TAG = "module_version_verify"

View File

@@ -36,7 +36,6 @@ import android.graphics.drawable.Icon
import android.graphics.drawable.VectorDrawable import android.graphics.drawable.VectorDrawable
import android.service.notification.StatusBarNotification import android.service.notification.StatusBarNotification
import android.util.ArrayMap import android.util.ArrayMap
import android.util.ArraySet
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewOutlineProvider import android.view.ViewOutlineProvider
@@ -56,6 +55,7 @@ import com.fankes.coloros.notify.utils.factory.*
import com.fankes.coloros.notify.utils.tool.IconAdaptationTool import com.fankes.coloros.notify.utils.tool.IconAdaptationTool
import com.highcapable.yukihookapi.hook.bean.VariousClass import com.highcapable.yukihookapi.hook.bean.VariousClass
import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
import com.highcapable.yukihookapi.hook.factory.current
import com.highcapable.yukihookapi.hook.factory.field import com.highcapable.yukihookapi.hook.factory.field
import com.highcapable.yukihookapi.hook.factory.method import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.log.loggerD import com.highcapable.yukihookapi.hook.log.loggerD
@@ -144,6 +144,12 @@ class SystemUIHooker : YukiBaseHooker() {
"com.coloros.systemui.common.receiver.AbstractReceiver" "com.coloros.systemui.common.receiver.AbstractReceiver"
) )
/** 根据多个版本存在不同的包名相同的类 */
private val StatusBarNotificationPresenterClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.phone.StatusBarNotificationPresenter",
"$SYSTEMUI_PACKAGE_NAME.statusbar.phone.StatusBar"
)
/** 根据多个版本存在不同的包名相同的类 */ /** 根据多个版本存在不同的包名相同的类 */
private val ExpandableNotificationRowClass = VariousClass( private val ExpandableNotificationRowClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.ExpandableNotificationRow", "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.ExpandableNotificationRow",
@@ -175,8 +181,8 @@ class SystemUIHooker : YukiBaseHooker() {
/** 状态栏通知图标数组 */ /** 状态栏通知图标数组 */
private var notificationIconInstances = ArrayList<View>() private var notificationIconInstances = ArrayList<View>()
/** 缓存的通知小图标包装纸实例 */ /** 通知栏通知控制器 */
private var notificationViewWrappers = ArraySet<Any>() private var notificationPresenter: Any? = null
/** 仅监听一次主题壁纸颜色变化 */ /** 仅监听一次主题壁纸颜色变化 */
private var isWallpaperColorListenerSetUp = false private var isWallpaperColorListenerSetUp = false
@@ -334,8 +340,11 @@ class SystemUIHooker : YukiBaseHooker() {
/** 刷新通知小图标 */ /** 刷新通知小图标 */
private fun refreshNotificationIcons() = runInSafe { private fun refreshNotificationIcons() = runInSafe {
NotificationHeaderViewWrapperClass.clazz.method { name = "resolveHeaderViews" }.also { result -> notificationPresenter?.current {
notificationViewWrappers.takeIf { it.isNotEmpty() }?.forEach { result.get(it).call() } method {
name = "updateNotificationsOnDensityOrFontScaleChanged"
emptyParam()
}.call()
} }
} }
@@ -549,7 +558,7 @@ class SystemUIHooker : YukiBaseHooker() {
} }
beforeHook { beforeHook {
/** 是否移除 */ /** 是否移除 */
if (args().int() == 7 && prefs.get(DataConst.REMOVE_CHANGECP_NOTIFY)) resultNull() if (args().first().int() == 7 && prefs.get(DataConst.REMOVE_CHANGECP_NOTIFY)) resultNull()
} }
} }
} }
@@ -573,7 +582,7 @@ class SystemUIHooker : YukiBaseHooker() {
name = "isGrayscaleOplus" name = "isGrayscaleOplus"
param(ImageViewClass, OplusContrastColorUtilClass) param(ImageViewClass, OplusContrastColorUtilClass)
} }
replaceAny { firstArgs<ImageView>()?.let { isGrayscaleIcon(it.context, it.drawable) } } replaceAny { args().first().cast<ImageView>()?.let { isGrayscaleIcon(it.context, it.drawable) } }
}.ignoredHookingFailure() }.ignoredHookingFailure()
} }
/** 替换状态栏图标 */ /** 替换状态栏图标 */
@@ -588,7 +597,7 @@ class SystemUIHooker : YukiBaseHooker() {
.get(field { name = "iconBuilder" }.get(instance).cast()).cast<Context>()?.also { context -> .get(field { name = "iconBuilder" }.get(instance).cast()).cast<Context>()?.also { context ->
NotificationEntryClass.clazz.method { NotificationEntryClass.clazz.method {
name = "getSbn" name = "getSbn"
}.get(firstArgs).invoke<StatusBarNotification>()?.also { nf -> }.get(args[0]).invoke<StatusBarNotification>()?.also { nf ->
nf.notification.smallIcon.loadDrawable(context).also { iconDrawable -> nf.notification.smallIcon.loadDrawable(context).also { iconDrawable ->
compatStatusIcon( compatStatusIcon(
context = context, context = context,
@@ -620,7 +629,7 @@ class SystemUIHooker : YukiBaseHooker() {
param(StatusBarNotificationClass) param(StatusBarNotificationClass)
} }
afterHook { afterHook {
if (firstArgs != null) instance<ImageView>().also { if (args[0] != null) instance<ImageView>().also {
/** 注册壁纸颜色监听 */ /** 注册壁纸颜色监听 */
registerWallpaperColorChanged(it) registerWallpaperColorChanged(it)
/** 注册广播 */ /** 注册广播 */
@@ -629,6 +638,13 @@ class SystemUIHooker : YukiBaseHooker() {
} }
} }
} }
/** 注入通知控制器实例 */
StatusBarNotificationPresenterClass.hook {
injectMember {
allConstructors()
afterHook { notificationPresenter = instance }
}
}
/** 注入状态栏通知图标容器实例 */ /** 注入状态栏通知图标容器实例 */
OplusNotificationIconAreaControllerClass.hook { OplusNotificationIconAreaControllerClass.hook {
injectMember { injectMember {
@@ -684,11 +700,6 @@ class SystemUIHooker : YukiBaseHooker() {
} }
} }
} }
/** 记录实例 */
injectMember {
constructor { param(ContextClass, ViewClass, ExpandableNotificationRowClass) }
afterHook { notificationViewWrappers.add(instance) }
}
} }
/** 发送适配新的 APP 图标通知 */ /** 发送适配新的 APP 图标通知 */
PluginManagerImplClass.hook { PluginManagerImplClass.hook {
@@ -698,7 +709,7 @@ class SystemUIHooker : YukiBaseHooker() {
param(ContextClass, IntentClass) param(ContextClass, IntentClass)
} }
afterHook { afterHook {
if (isEnableHookColorNotifyIcon()) (lastArgs as? Intent)?.also { if (isEnableHookColorNotifyIcon()) args().last().cast<Intent>()?.also {
if (it.action.equals(Intent.ACTION_PACKAGE_REPLACED).not() && if (it.action.equals(Intent.ACTION_PACKAGE_REPLACED).not() &&
it.getBooleanExtra(Intent.EXTRA_REPLACING, false) it.getBooleanExtra(Intent.EXTRA_REPLACING, false)
) return@also ) return@also
@@ -708,11 +719,11 @@ class SystemUIHooker : YukiBaseHooker() {
if (iconDatas.takeIf { e -> e.isNotEmpty() } if (iconDatas.takeIf { e -> e.isNotEmpty() }
?.filter { e -> e.packageName == newPkgName } ?.filter { e -> e.packageName == newPkgName }
.isNullOrEmpty() .isNullOrEmpty()
) IconAdaptationTool.pushNewAppSupportNotify(firstArgs()!!, newPkgName) ) IconAdaptationTool.pushNewAppSupportNotify(args().first().cast()!!, newPkgName)
} }
Intent.ACTION_PACKAGE_REMOVED -> Intent.ACTION_PACKAGE_REMOVED ->
IconAdaptationTool.removeNewAppSupportNotify( IconAdaptationTool.removeNewAppSupportNotify(
context = firstArgs()!!, context = args().first().cast()!!,
packageName = it.data?.schemeSpecificPart ?: "" packageName = it.data?.schemeSpecificPart ?: ""
) )
} }
@@ -728,7 +739,7 @@ class SystemUIHooker : YukiBaseHooker() {
param(ContextClass, IntentClass) param(ContextClass, IntentClass)
} }
afterHook { afterHook {
firstArgs<Context>()?.also { args().first().cast<Context>()?.also {
/** 注册广播 */ /** 注册广播 */
registerReceiver(it) registerReceiver(it)
/** 注册定时监听 */ /** 注册定时监听 */

View File

@@ -33,6 +33,7 @@ import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Configuration import android.content.res.Configuration
@@ -193,6 +194,14 @@ fun Context.findAppName(name: String) =
fun Context.findAppIcon(name: String) = fun Context.findAppIcon(name: String) =
safeOfNull { packageManager?.getPackageInfo(name, 0)?.applicationInfo?.loadIcon(packageManager) } safeOfNull { packageManager?.getPackageInfo(name, 0)?.applicationInfo?.loadIcon(packageManager) }
/**
* 获取 APP 是否为 DEBUG 版本
* @param name APP 包名
* @return [Boolean]
*/
fun Context.isAppDebuggable(name: String) =
safeOfFalse { (packageManager?.getPackageInfo(name, 0)?.applicationInfo?.flags?.and(ApplicationInfo.FLAG_DEBUGGABLE) ?: 0) != 0 }
/** /**
* 对数值自动补零 * 对数值自动补零
* @return [String] * @return [String]
@@ -320,12 +329,19 @@ fun findPropString(key: String, default: String = "") = safeOf(default) {
} }
/** /**
* 执行命令 - su * 是否有 Root 权限
* @return [Boolean]
*/
val isRootAccess get() = safeOfFalse { Shell.rootAccess() }
/**
* 执行命令
* @param cmd 命令 * @param cmd 命令
* @param isSu 是否使用 Root 权限执行 - 默认:是
* @return [String] 执行结果 * @return [String] 执行结果
*/ */
fun execShellSu(cmd: String) = safeOfNothing { fun execShell(cmd: String, isSu: Boolean = true) = safeOfNothing {
Shell.su(cmd).exec().out.let { (if (isSu) Shell.su(cmd) else Shell.sh(cmd)).exec().out.let {
if (it.isNotEmpty()) it[0].trim() else "" if (it.isNotEmpty()) it[0].trim() else ""
} }
} }

View File

@@ -116,6 +116,7 @@ object IconAdaptationTool {
* @param packageName 安装的 APP 包名 * @param packageName 安装的 APP 包名
*/ */
fun pushNewAppSupportNotify(context: Context, packageName: String) { fun pushNewAppSupportNotify(context: Context, packageName: String) {
if (context.isAppDebuggable(packageName)) return
context.getSystemService(NotificationManager::class.java)?.apply { context.getSystemService(NotificationManager::class.java)?.apply {
createNotificationChannel( createNotificationChannel(
NotificationChannel( NotificationChannel(

View File

@@ -86,9 +86,9 @@ object SystemUITool {
msg = "你确定要立即重启系统界面吗?\n\n" + msg = "你确定要立即重启系统界面吗?\n\n" +
"重启过程会黑屏并等待进入锁屏重新解锁。" "重启过程会黑屏并等待进入锁屏重新解锁。"
confirmButton { confirmButton {
execShellSu(cmd = "pgrep systemui").also { pid -> execShell(cmd = "pgrep systemui").also { pid ->
if (pid.isNotBlank()) if (pid.isNotBlank())
execShellSu(cmd = "kill -9 $pid") execShell(cmd = "kill -9 $pid")
else toast(msg = "ROOT 权限获取失败") else toast(msg = "ROOT 权限获取失败")
} }
} }

View File

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