Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README-zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@

由于本人同时维护 **HyperOS/MIUI** 与 **ColorOS** 两个系统需要同时维护两个模块,十分不方便,所以我决定在后期逐渐合并两个项目并解耦合为一个新项目并计划适配更多系统与设备,例如原生与类原生系统。

在新的项目确定后,会在这里添加新项目的链接,届时我会终止维护这个项目并建议大家转移到新项目。
新项目已确定为 [Sumikage](https://github.com/fankes/Sumikage),目前处于开发阶段,在新项目发布正式版本后,我会终止维护这个项目并建议大家转移到新项目。

此模块中与通知图标无关的功能 (例如移除开发者选项警告通知、移除充电完成通知) 不会被迁移到新项目。

## 适配说明

Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")

Expand Down Expand Up @@ -365,6 +369,9 @@ object SystemUIHooker : YukiBaseHooker() {
/** 记录已处理过的图标 [ImageView] - 值 true 表示本模块接管,false 表示交还系统;用于拦截判断与避免重复处理 */
private val moduleStyledIcons = WeakHashMap<ImageView, Boolean>()

/** 模块接管后的图标 [Drawable] - 系统再次设置图标时强制换回 */
private val moduleStyledDrawables = WeakHashMap<ImageView, Drawable>()

/**
* 通知面板图标内边距比例 (MD3 风格) - 内边距占图标视图尺寸的比例 (0f~0.5f)
*
Expand Down Expand Up @@ -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
)
Expand Down Expand Up @@ -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 {
// 标记为已处理但非本模块接管
Expand Down Expand Up @@ -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 实例
Expand Down Expand Up @@ -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))
}
}
}
}
Expand Down Expand Up @@ -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<ImageView>()
if (moduleStyledIcons[iconView] == true) {
moduleStyledDrawables[iconView]?.let { styled ->
if (arg(0).get<Drawable>() !== styled) arg(0).set(styled)
}
}
}

OplusNotificationGroupTemplateWrapperClass?.resolve()?.optional()?.apply {
Expand Down
Loading