From 82447c527d1911d16a543ba3a98e523ae26fb9b1 Mon Sep 17 00:00:00 2001 From: fankesyooni Date: Tue, 27 Oct 2026 19:25:52 +0800 Subject: [PATCH 1/3] docs: update README --- README-zh-CN.md | 4 +++- README.md | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/README-zh-CN.md b/README-zh-CN.md index 5fe420ea..290993f6 100644 --- a/README-zh-CN.md +++ b/README-zh-CN.md @@ -22,7 +22,9 @@ 由于本人同时维护 **HyperOS/MIUI** 与 **ColorOS** 两个系统需要同时维护两个模块,十分不方便,所以我决定在后期逐渐合并两个项目并解耦合为一个新项目并计划适配更多系统与设备,例如原生与类原生系统。 -在新的项目确定后,会在这里添加新项目的链接,届时我会终止维护这个项目并建议大家转移到新项目。 +新项目已确定为 [Sumikage](https://github.com/fankes/Sumikage),目前处于开发阶段,在新项目发布正式版本后,我会终止维护这个项目并建议大家转移到新项目。 + +此模块中与通知图标无关的功能 (例如移除开发者选项警告通知、移除充电完成通知) 不会被迁移到新项目。 ## 适配说明 diff --git a/README.md b/README.md index 3669e7e2..b3a39769 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,11 @@ Maintaining two modules for both **HyperOS/MIUI** and **ColorOS** at the same ti two projects into a new one, with plans to support more systems and devices, such as AOSP and AOSP-like systems. -Once the new project is settled, its link will be added here. At that time I will stop maintaining this project and recommend that everyone move to -the new project. +The new project is [Sumikage](https://github.com/fankes/Sumikage), which is currently in development. After the new project publishes its official +release, I will stop maintaining this project and recommend that everyone move to the new project. + +Features of this module that are unrelated to notification icons (for example, removing the developer options warning notification and the +charging complete notification) will not be migrated to the new project. ## Compatibility Notes From b12c51f94b2befc2efb93f11c8b8e62787744067 Mon Sep 17 00:00:00 2001 From: h465855hgg <3382198490@qq.com> Date: Wed, 28 Oct 2026 10:35:43 +0800 Subject: [PATCH 2/3] fix: intercept ColorOS 16 fixAppIcon app-icon substitution ColorOS 16.0.5 ships OplusNotificationFixHelper.fixAppIcon and keeps replacing notification icons with app icons even though fixSmallIcon is intercepted. Intercept both. --- .../coloros/notify/hook/entity/FrameworkHooker.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/fankes/coloros/notify/hook/entity/FrameworkHooker.kt b/app/src/main/java/com/fankes/coloros/notify/hook/entity/FrameworkHooker.kt index 9c7e239e..adfd1aed 100644 --- a/app/src/main/java/com/fankes/coloros/notify/hook/entity/FrameworkHooker.kt +++ b/app/src/main/java/com/fankes/coloros/notify/hook/entity/FrameworkHooker.kt @@ -46,10 +46,14 @@ object FrameworkHooker : YukiBaseHooker() { override fun onHook() { // 拦截 ColorOS 覆盖应用通知图标 - OplusNotificationFixHelperClass?.resolve()?.optional()?.firstMethodOrNull { - name = "fixSmallIcon" - parameters(Notification::class, String::class, String::class, Boolean::class) - }?.hook()?.intercept() + OplusNotificationFixHelperClass?.resolve()?.optional()?.apply { + firstMethodOrNull { + name = "fixSmallIcon" + parameters(Notification::class, String::class, String::class, Boolean::class) + }?.hook()?.intercept() + // ColorOS 16 起改用 fixAppIcon 继续把通知图标替换成应用图标 + firstMethodOrNull { name = "fixAppIcon" }?.hook()?.intercept() + } // ColorOS 自启动管理会拦截 SystemUI 冷启动模块的定时更新服务,这里只放行这一个服务 OplusAppStartupManagerClass?.resolve()?.optional()?.firstMethodOrNull { name = "isAllowStartFromStartService" From f360c303a2578021caf14ac7f17a36b47a438341 Mon Sep 17 00:00:00 2001 From: h465855hgg <3382198490@qq.com> Date: Wed, 28 Oct 2026 10:35:43 +0800 Subject: [PATCH 3/3] fix: keep module notification icons on ColorOS 16 - normalize replaced status bar icons to the ColorOS 20dp slot (0.8x) so they match the system icon size - enforce the module drawable through ImageView.setImageDrawable when the system resets the icon after styling - restyle conversation notifications after updateIconWithAppIconIfNeeded - pick readable glyph colors for light badges and light panels --- .../notify/hook/entity/SystemUIHooker.kt | 84 ++++++++++++++++--- 1 file changed, 74 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/fankes/coloros/notify/hook/entity/SystemUIHooker.kt b/app/src/main/java/com/fankes/coloros/notify/hook/entity/SystemUIHooker.kt index 48e59417..9eef9988 100644 --- a/app/src/main/java/com/fankes/coloros/notify/hook/entity/SystemUIHooker.kt +++ b/app/src/main/java/com/fankes/coloros/notify/hook/entity/SystemUIHooker.kt @@ -65,6 +65,7 @@ import com.fankes.coloros.notify.utils.factory.colorAlphaOf import com.fankes.coloros.notify.utils.factory.delayedRun import com.fankes.coloros.notify.utils.factory.isSystemInDarkMode import com.fankes.coloros.notify.utils.factory.isUpperOfColorOS17 +import com.fankes.coloros.notify.utils.factory.isWhiteColor import com.fankes.coloros.notify.utils.factory.launcherActivitiesOf import com.fankes.coloros.notify.utils.factory.launcherIconOf import com.fankes.coloros.notify.utils.factory.runInSafe @@ -126,6 +127,9 @@ object SystemUIHooker : YukiBaseHooker() { /** 原生存在的类 */ private val NotificationEntryClass by lazyClass("${PackageName.SYSTEMUI}.statusbar.notification.collection.NotificationEntry") + /** 原生存在的类 */ + private val ImageViewClass by lazyClass("android.widget.ImageView") + /** 原生存在的类 */ private val StatusBarIconClass by lazyClass("com.android.internal.statusbar.StatusBarIcon") @@ -365,6 +369,9 @@ object SystemUIHooker : YukiBaseHooker() { /** 记录已处理过的图标 [ImageView] - 值 true 表示本模块接管,false 表示交还系统;用于拦截判断与避免重复处理 */ private val moduleStyledIcons = WeakHashMap() + /** 模块接管后的图标 [Drawable] - 系统再次设置图标时强制换回 */ + private val moduleStyledDrawables = WeakHashMap() + /** * 通知面板图标内边距比例 (MD3 风格) - 内边距占图标视图尺寸的比例 (0f~0.5f) * @@ -721,25 +728,27 @@ object SystemUIHooker : YukiBaseHooker() { // 自定义通知图标颜色 val customIconColor = if (ConfigData.isEnableNotifyIconForceSystemColor) 0 else customTriple.second - // 旧版图标着色 + // 旧版图标着色 - 浅色面板上白色 / 浅色字会看不见,按面板明暗回退 val oldApplyColor = customIconColor.takeIf { it != 0 } ?: nativeIconColor.takeIf { it != 0 } ?: oldStyle + val oldReadableColor = if (oldApplyColor.isWhiteColor && context.isSystemInDarkMode.not()) oldStyle else oldApplyColor - // 新版图标 (背景) 着色 - val newApplyColor = customIconColor.takeIf { it != 0 } ?: nativeIconColor.takeIf { it != 0 } ?: md3Style + // 新版图标 (背景) 着色 - 颜色透明时兜底,避免只有白色图标没有背景 + val newApplyColor = (customIconColor.takeIf { it != 0 } ?: nativeIconColor.takeIf { it != 0 } ?: md3Style) + .takeIf { Color.alpha(it) != 0 } ?: md3Style.takeIf { Color.alpha(it) != 0 } ?: 0xFF707173.toInt() // 用自绘 [CustomIconDrawable] 承载 “背景 + 着色图标”,其内部把 setColorFilter/setTint 覆盖为重申自身颜色, // 背景也画在图标自身而非视图 background return if (isMd3) CustomIconDrawable( glyph = glyph, badgeColor = newApplyColor, - glyphColor = if (newApplyColor == -1) Color.BLACK else Color.WHITE, + glyphColor = if (newApplyColor.isWhiteColor) Color.BLACK else Color.WHITE, cornerRadiusPx = ConfigData.notifyIconCornerSize.toPx(context).toFloat(), paddingRatio = padRatio, referenceSidePx = if (viewSizePx > 0) viewSizePx.toFloat() else headerIconSizeOf(context).toFloat() ) else CustomIconDrawable( glyph = glyph, badgeColor = Color.TRANSPARENT, - glyphColor = oldApplyColor, + glyphColor = oldReadableColor, cornerRadiusPx = 0f, paddingRatio = padRatio ) @@ -791,7 +800,16 @@ object SystemUIHooker : YukiBaseHooker() { clearColorFilter() // 图标视图尺寸 val viewSizePx = layoutParams?.width?.takeIf { it > 0 } ?: width.takeIf { it > 0 } ?: 0 - setImageDrawable(customNotifyIconOf(context, customTriple, drawable, iconColor, viewSizePx)) + val styled = customNotifyIconOf(context, customTriple, drawable, iconColor, viewSizePx) + moduleStyledDrawables[this] = styled + setImageDrawable(styled) + // ColorOS 16 会在布局/颜色刷新后把系统图标重新设回来,下一帧检查并补一次 + post { + if (drawable !== styled) { + YLog.debug("Icon drawable was replaced by system, re-applying: $realPackageName") + setImageDrawable(styled) + } + } } else -> iconView.apply { // 标记为已处理但非本模块接管 @@ -964,6 +982,30 @@ object SystemUIHooker : YukiBaseHooker() { } } + /** + * 把替换用的图标缩放到状态栏图标尺寸 + * + * ColorOS 16 起状态栏图标由 IconManager 创建,旧的重绘路径不再执行, + * 位图的固有尺寸(ANIP 72px / 桌面图标 108dp)会直接参与系统缩放 + * @param context 实例 + * @return [Bitmap] + */ + @Suppress("DiscouragedApi") + private fun Drawable.toStatusBarBitmap(context: Context): Bitmap { + val res = context.resources + fun dimenOf(name: String, pkg: String) = + res.getIdentifier(name, "dimen", pkg).takeIf { it != 0 }?.let { res.getDimensionPixelSize(it) } ?: 0 + // ColorOS 16 的状态栏槽位是 20dp(不是 AOSP 的 22dp),且系统按 0.75 缩放应用图标, + // 蒙版直接铺满槽位会比系统图标大一圈,这里按槽位的 0.8 倍出图 + val slot = dimenOf("status_bar_icon_size", "com.android.systemui").takeIf { it > 0 } + ?: dimenOf("status_bar_icon_size", "android").takeIf { it > 0 } + ?: (20 * res.displayMetrics.density).toInt() + val target = (slot * 0.8f).toInt().coerceAtLeast(1) + val bitmap = toBitmap() + return if (bitmap.width != target || bitmap.height != target) + Bitmap.createScaledBitmap(bitmap, target, target, true) else bitmap + } + /** * 取通知面板头部图标尺寸 - 圆角设置针对的图标大小 * @param context 实例 @@ -1510,10 +1552,13 @@ object SystemUIHooker : YukiBaseHooker() { packageName = nf.packageName, drawable = iconDrawable ).also { pair -> - if (pair.second) StatusBarIconClass.resolve().optional().firstFieldOrNull { - name = "icon" - type = Icon::class - }?.of(result)?.set(Icon.createWithBitmap(pair.first.toBitmap())) + if (pair.second) { + val bitmap = pair.first.toStatusBarBitmap(context) + StatusBarIconClass.resolve().optional().firstFieldOrNull { + name = "icon" + type = Icon::class + }?.of(result)?.set(Icon.createWithBitmap(bitmap)) + } } } } @@ -1743,6 +1788,25 @@ object SystemUIHooker : YukiBaseHooker() { after { headerIconOf(instance)?.let { styleHeaderIcon(instance) } } // after { headerIconOf(instance)?.let { if (!moduleStyledIcons.containsKey(it)) styleHeaderIcon(instance) } } } + // ColorOS 16 对带会话头像的通知(如 QQ)会单独走这个方法把图标换回去,处理完再套一次模块样式 + firstMethodOrNull { + name = "updateIconWithAppIconIfNeeded" + }?.hook()?.after { + runInSafe { headerIconOf(instance)?.let { styleHeaderIcon(instance) } } + } + } + + // 系统会在任意时机把通知图标重新设成自己的 drawable,这里在框架层兜住 + ImageViewClass.resolve().optional().firstMethodOrNull { + name = "setImageDrawable" + parameters(Drawable::class) + }?.hook()?.before { + val iconView = instance() + if (moduleStyledIcons[iconView] == true) { + moduleStyledDrawables[iconView]?.let { styled -> + if (arg(0).get() !== styled) arg(0).set(styled) + } + } } OplusNotificationGroupTemplateWrapperClass?.resolve()?.optional()?.apply {