From 2424481f182961f2d04997ddb233223e7e83c27e Mon Sep 17 00:00:00 2001
From: Ashurinnn <77261372+Ashurinnn@users.noreply.github.com>
Date: Wed, 14 Oct 2026 18:28:08 +0100
Subject: [PATCH] feat(systemui): add Wi-Fi activity and mobile network type
hiding
Add two opt-in status-bar controls with searchable settings and native
HyperOS presentation hooks. Keep the dual-SIM implementation and tests
commented out for later repair, and document the verified feature scope.
---
README.md | 2 +
app/src/main/AndroidManifest.xml | 2 +
.../ui/screen/about/LicensePage.kt | 2 +-
.../ui/screen/features/FeaturesPage.kt | 7 +
.../screen/features/NetworkIconsActivity.kt | 38 ++
.../ui/screen/features/StatusBarOptions.kt | 23 ++
.../ui/screen/features/SystemUiActivity.kt | 7 +-
app/src/main/res/values-en/strings.xml | 5 +
app/src/main/res/values/strings.xml | 5 +
changelog.md | 1 +
docs/CUSTOMIZE.md | 2 +
docs/STATUSBAR_PORT.md | 30 ++
.../hyperrefine/hook/systemui/SystemUiLoad.kt | 5 +
.../statusbar/DualSimArtworkPolicy.kt | 61 +++
.../statusbar/DualSimRuntimeSupport.kt | 58 +++
.../systemui/statusbar/DualSimSignalHook.kt | 363 ++++++++++++++++++
.../systemui/statusbar/MobileTypeHooks.kt | 28 ++
.../statusbar/StackedSignalDrawable.kt | 65 ++++
.../hook/systemui/statusbar/StatusBarKeys.kt | 16 +
.../systemui/statusbar/StatusBarPolicy.kt | 29 ++
.../systemui/statusbar/StatusBarSupport.kt | 99 +++++
.../hook/systemui/statusbar/StatusBarViews.kt | 21 +
.../systemui/statusbar/WifiIndicatorHooks.kt | 39 ++
.../statusbar/DualSimArtworkPolicyTest.kt | 84 ++++
.../statusbar/DualSimRuntimeSupportTest.kt | 96 +++++
.../systemui/statusbar/StatusBarPolicyTest.kt | 53 +++
26 files changed, 1139 insertions(+), 2 deletions(-)
create mode 100644 app/src/main/java/cn/ianzb/hyperrefine/ui/screen/features/NetworkIconsActivity.kt
create mode 100644 app/src/main/java/cn/ianzb/hyperrefine/ui/screen/features/StatusBarOptions.kt
create mode 100644 docs/STATUSBAR_PORT.md
create mode 100644 hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/DualSimArtworkPolicy.kt
create mode 100644 hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/DualSimRuntimeSupport.kt
create mode 100644 hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/DualSimSignalHook.kt
create mode 100644 hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/MobileTypeHooks.kt
create mode 100644 hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/StackedSignalDrawable.kt
create mode 100644 hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/StatusBarKeys.kt
create mode 100644 hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/StatusBarPolicy.kt
create mode 100644 hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/StatusBarSupport.kt
create mode 100644 hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/StatusBarViews.kt
create mode 100644 hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/WifiIndicatorHooks.kt
create mode 100644 hook/src/test/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/DualSimArtworkPolicyTest.kt
create mode 100644 hook/src/test/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/DualSimRuntimeSupportTest.kt
create mode 100644 hook/src/test/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/StatusBarPolicyTest.kt
diff --git a/README.md b/README.md
index 30ad26a..d128b82 100644
--- a/README.md
+++ b/README.md
@@ -170,6 +170,8 @@ HyperRefine 基于个人 Android 模块开发脚手架 MiuixGuiTemplate 创建
通知功能独立实现,未引入参考模块的代码、资源或原生库;参考项目的来源与许可证已登记到应用内参考项目分组。
+状态栏的 Wi-Fi 活动指示器隐藏与移动网络类型隐藏同样独立实现,参考来源统一登记在应用内致谢页;功能范围、验证和暂未启用的双卡双行源码见 [状态栏功能说明](docs/STATUSBAR_PORT.md)。
+
# 赞助支持
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4c5cce9..5d87c8b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -73,6 +73,8 @@
android:name=".ui.screen.features.NotificationsActivity"
android:exported="false"
android:theme="@style/Theme.HyperRefine" />
+
= listOf(
LibraryInfo(
"Pengeek(米客)",
"OS4-26.10.13-test",
- "GPL-3.0,仅通知功能思路参考(独立实现,未复用其代码或资源)",
+ "GPL-3.0,通知与状态栏功能思路参考(独立实现,未复用其代码或资源)",
"https://github.com/MonwF/customiuizer",
),
LibraryInfo(
diff --git a/app/src/main/java/cn/ianzb/hyperrefine/ui/screen/features/FeaturesPage.kt b/app/src/main/java/cn/ianzb/hyperrefine/ui/screen/features/FeaturesPage.kt
index ead76e8..17eee87 100644
--- a/app/src/main/java/cn/ianzb/hyperrefine/ui/screen/features/FeaturesPage.kt
+++ b/app/src/main/java/cn/ianzb/hyperrefine/ui/screen/features/FeaturesPage.kt
@@ -72,6 +72,7 @@ fun FeaturesPageView(
HookSubPage(
titleRes = R.string.feature_system_ui,
specs = listOf(
+ specByKey(specs, KEY_STATUS_BAR),
specByKey(specs, KEY_GLASS),
specByKey(specs, KEY_CC_RADIUS),
specByKey(specs, PercentLocation.entryKey(PercentLocation.CC_BRIGHTNESS)),
@@ -88,6 +89,11 @@ fun FeaturesPageView(
),
onOpen = { context.startActivity(Intent(context, SystemUiActivity::class.java)) },
subPages = listOf(
+ HookSubPage(
+ titleRes = R.string.sb_title,
+ specs = statusBarSpecs(specs),
+ onOpen = { context.startActivity(Intent(context, NetworkIconsActivity::class.java)) },
+ ),
HookSubPage(
titleRes = R.string.notifications_title,
specs = notificationSpecs(specs),
@@ -1223,6 +1229,7 @@ internal fun featureSpecs(): List {
specs += buildAutoBalanceSpecs(systemUi)
specs += ccRadiusSpecs(systemUi)
specs += notificationFeatureSpecs()
+ specs += statusBarFeatureSpecs()
return specs
}
diff --git a/app/src/main/java/cn/ianzb/hyperrefine/ui/screen/features/NetworkIconsActivity.kt b/app/src/main/java/cn/ianzb/hyperrefine/ui/screen/features/NetworkIconsActivity.kt
new file mode 100644
index 0000000..b12749a
--- /dev/null
+++ b/app/src/main/java/cn/ianzb/hyperrefine/ui/screen/features/NetworkIconsActivity.kt
@@ -0,0 +1,38 @@
+package cn.ianzb.hyperrefine.ui.screen.features
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import cn.ianzb.hyperrefine.R
+import cn.ianzb.hyperrefine.ui.component.pref.HookOptionView
+import cn.ianzb.hyperrefine.ui.component.pref.HookSection
+import cn.ianzb.hyperrefine.ui.component.pref.HookSectionCard
+import cn.ianzb.hyperrefine.ui.screen.subpage.BaseSubPageActivity
+import cn.ianzb.hyperrefine.ui.util.LocalSubPageScrollBehavior
+import cn.ianzb.hyperrefine.ui.util.pageScrollModifiers
+
+class NetworkIconsActivity : BaseSubPageActivity() {
+ override val titleRes = R.string.sb_title
+ @Composable
+ override fun SubPageContent(isBlurEnabled: Boolean, contentPadding: PaddingValues) {
+ val specs = statusBarSpecs(featureSpecs())
+ StatusBarSections(listOf(
+ HookSection(R.string.sb_network_section, specs = specs.take(1)),
+ HookSection(R.string.sb_mobile_type_section, specs = specs.drop(1)),
+ ), contentPadding)
+ }
+}
+
+@Composable
+private fun StatusBarSections(sections: List, padding: PaddingValues) {
+ val scroll = LocalSubPageScrollBehavior.current
+ LazyColumn(modifier = Modifier.fillMaxSize().then(
+ if (scroll != null) Modifier.pageScrollModifiers(showTopAppBar = true, topAppBarScrollBehavior = scroll) else Modifier,
+ ), contentPadding = padding) {
+ sections.forEach { section -> item {
+ HookSectionCard(section) { section.specs.forEach { spec -> HookOptionView(spec) } }
+ } }
+ }
+}
diff --git a/app/src/main/java/cn/ianzb/hyperrefine/ui/screen/features/StatusBarOptions.kt b/app/src/main/java/cn/ianzb/hyperrefine/ui/screen/features/StatusBarOptions.kt
new file mode 100644
index 0000000..6cca6fb
--- /dev/null
+++ b/app/src/main/java/cn/ianzb/hyperrefine/ui/screen/features/StatusBarOptions.kt
@@ -0,0 +1,23 @@
+package cn.ianzb.hyperrefine.ui.screen.features
+
+import cn.ianzb.hyperrefine.R
+import cn.ianzb.hyperrefine.hook.systemui.statusbar.StatusBarKeys as Keys
+import cn.ianzb.hyperrefine.prefs.OptionSpec
+import cn.ianzb.hyperrefine.prefs.OptionType
+
+const val KEY_STATUS_BAR = "feature_statusbar"
+
+internal fun statusBarFeatureSpecs(): List {
+ fun toggle(key: String, title: Int) = OptionSpec(
+ key = key, type = OptionType.SWITCH, titleRes = title,
+ defaultBoolean = false, targetPackages = listOf(Keys.TARGET), showStatus = true,
+ )
+ return listOf(
+ OptionSpec(key = KEY_STATUS_BAR, type = OptionType.ARROW, titleRes = R.string.sb_title),
+ toggle(Keys.WIFI_ACTIVITY, R.string.sb_wifi_hide_activity),
+ toggle(Keys.HIDE_TYPE, R.string.sb_mobile_hide_type),
+ )
+}
+
+internal fun statusBarSpecs(specs: List) =
+ Keys.ALL.map { key -> specs.first { it.key == key } }
diff --git a/app/src/main/java/cn/ianzb/hyperrefine/ui/screen/features/SystemUiActivity.kt b/app/src/main/java/cn/ianzb/hyperrefine/ui/screen/features/SystemUiActivity.kt
index 144a271..264a28f 100644
--- a/app/src/main/java/cn/ianzb/hyperrefine/ui/screen/features/SystemUiActivity.kt
+++ b/app/src/main/java/cn/ianzb/hyperrefine/ui/screen/features/SystemUiActivity.kt
@@ -39,6 +39,10 @@ class SystemUiActivity : BaseSubPageActivity() {
) {
val context = LocalContext.current
val scrollBehavior = LocalSubPageScrollBehavior.current
+ val statusBarSection = HookSection(
+ titleRes = R.string.sb_title,
+ specs = listOf(featureSpec(KEY_STATUS_BAR)),
+ )
val controlCenterSection = HookSection(
titleRes = R.string.feature_control_center,
specs = listOf(
@@ -81,6 +85,7 @@ class SystemUiActivity : BaseSubPageActivity() {
val openSubPage: (String) -> Unit = { key ->
when (key) {
+ KEY_STATUS_BAR -> context.startActivity(Intent(context, NetworkIconsActivity::class.java))
KEY_GLASS ->
context.startActivity(Intent(context, GlassActivity::class.java))
KEY_CC_RADIUS ->
@@ -125,7 +130,7 @@ class SystemUiActivity : BaseSubPageActivity() {
),
contentPadding = contentPadding,
) {
- listOf(controlCenterSection, volumeSection, lockAodSection, wallpaperSection, windowSection).forEach { section ->
+ listOf(statusBarSection, controlCenterSection, volumeSection, lockAodSection, wallpaperSection, windowSection).forEach { section ->
item {
HookSectionCard(section) {
section.specs.forEach { spec ->
diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml
index 54e965b..9342725 100644
--- a/app/src/main/res/values-en/strings.xml
+++ b/app/src/main/res/values-en/strings.xml
@@ -494,4 +494,9 @@
Passkey
Browser
+ Status bar
+ Network icons
+ Hide Wi-Fi activity indicators
+ Mobile network type
+ Hide network type completely
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 93eb815..fc2a6bb 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -494,4 +494,9 @@
通行密钥
浏览器
+ 状态栏
+ 网络图标
+ 隐藏 Wi-Fi 网络活动指示器
+ 移动网络类型
+ 完全隐藏网络类型
diff --git a/changelog.md b/changelog.md
index 0232d73..d3f18eb 100644
--- a/changelog.md
+++ b/changelog.md
@@ -6,6 +6,7 @@
### 新增
+- **状态栏 · 网络图标**(目标 `com.android.systemui`):新增「系统界面 → 状态栏」页,提供「隐藏 Wi-Fi 活动指示器」「完全隐藏网络类型」两项开关,默认关闭;双卡双行实现暂以注释源码保留,等待后续修复。
- **负一屏 · 过渡动画优化**(目标 `com.miui.personalassistant` / system_server):新增「负一屏」功能页。**过渡闪黑修复**消除打开 / 返回快速切换时的纯色闪黑;**上滑合并强制反向**修正返回动画被系统合并时先反向播一半的问题;**放行远程动画权限**(系统服务侧,需重启设备)让负一屏的远程过渡动画完整生效。思路参考 HyperAssistant(非开源,独立实现)。
- **负一屏 · 打开应用动画与过渡遮罩重绘**(目标 `com.miui.personalassistant`):**应用打开动画**让负一屏点开的应用从图标位置缩放展开(options 逐级剥离重试,失败回退框架动画);**过渡遮罩重绘**修复过渡中遮罩显示异常内容。
- **负一屏 · 返回缩回动画**(目标 `com.miui.personalassistant`):关闭应用回到负一屏时,部件快照从全屏缩回原位,接近末尾渐隐;需先开启「应用打开动画」。
diff --git a/docs/CUSTOMIZE.md b/docs/CUSTOMIZE.md
index 3543a57..6c63986 100644
--- a/docs/CUSTOMIZE.md
+++ b/docs/CUSTOMIZE.md
@@ -413,6 +413,8 @@ HookOptionsPage(
通知功能的参考来源已登记到 `LicensePage.kt` 的参考项目分组,注明 GPL-3.0、仅思路参考、未复用代码或资源。新增七项配置和进程范围见 [通知功能说明](NOTIFICATION_PORT.md)。
+状态栏功能的参考来源也在该分组登记;仅借鉴思路,未引入参考模块或小米 SystemUI 的代码、图片及原生库。两项开关的配置、目标进程和注释中的双卡双行实现见 [状态栏功能说明](STATUSBAR_PORT.md)。
+
本模板的定位是**个人 Android 模块项目的脚手架模板**。具体参考与致谢清单统一维护在应用内「第三方许可证与致谢」页面(`LicensePage.kt` → `licenses_section_refs` 分组),衍生项目**可增不可删**。
> 文档不展开对具体模块的借鉴说明;复用任何第三方代码前须先核对许可证,无许可证 / 非开源项目只能借鉴思路并致谢,完整流程见 [模块开发工作流](WORKFLOW.md)。
diff --git a/docs/STATUSBAR_PORT.md b/docs/STATUSBAR_PORT.md
new file mode 100644
index 0000000..899c3eb
--- /dev/null
+++ b/docs/STATUSBAR_PORT.md
@@ -0,0 +1,30 @@
+# 状态栏功能说明
+
+入口:**功能 → 系统界面 → 状态栏**。当前仅开放以下两项开关,默认关闭,目标均为 `com.android.systemui`;修改后重新加载系统界面进程生效。
+
+| 选项 | 配置键 | 效果 |
+|---|---|---|
+| 隐藏 Wi-Fi 网络活动指示器 | `statusbar_wifi_hide_activity` | 隐藏 Wi-Fi 的收发活动图标,保留信号与连接显示 |
+| 完全隐藏网络类型 | `statusbar_mobile_hide_type` | 隐藏移动网络类型图片、独立文字和特殊 5G 标识,保留原生信号布局 |
+
+## 实现与验证
+
+Wi-Fi Hook 在原生绑定与活动更新之后将对应活动视图隐藏,避免后续收发更新再次显示。移动网络类型 Hook 在原生蜂窝模型创建后,将四个类型显示 Flow 替换为宿主自身的只读 false Flow;兼容宿主经过优化的一参数构造与普通二参数构造。
+
+配置通过项目既有 `OptionSpec`、偏好与 Hook 状态机制接入;状态栏子页面继承 `BaseSubPageActivity`,登记到父页搜索,并使用 `NetworkIconsActivity`。没有新增权限、作用域或原生依赖。
+
+接口静态核对基于提供的 HyperOS `OS4.0.22.0.XPNCNXM` ROM,SystemUI `17.03.260226.r`;其他 ROM 的私有接口兼容性尚未验证。用户已确认本次精简测试 APK 的两个开关均生效、双 SIM 保持原生布局,从状态栏页下拉通知中心/控制中心时背景显示正常。单元测试覆盖配置键、反射成员解析和两种只读 Flow 构造签名;没有新增临时诊断日志。
+
+## 暂不启用双卡双行
+
+`hook/.../systemui/statusbar/` 中的 `DualSimSignalHook.kt`、`DualSimRuntimeSupport.kt`、`DualSimArtworkPolicy.kt`、`StackedSignalDrawable.kt`、`StatusBarPolicy.kt`、`StatusBarViews.kt` 及对应双排测试以逐行注释保留,供后续修复参考。相关偏好键和宿主 Pair 解析辅助也已注释。
+
+这些文件不生成运行时类;当前没有双排 Hook 注册、配置入口或搜索项,不读取该功能的旧偏好。系统继续使用原生 SIM 信号布局。以后恢复时须重新接入配置与注册,验证各卡信号强度、单双卡切换、数据卡切换、锁屏及主题/明暗表现;不能只取消某个文件的注释就视为可用。
+
+此前电脑端导出的 PNG/SVG 不是运行时依赖,未进入仓库或 APK。当前构建没有图标解包、图片落盘或系统资源替换。
+
+## 参考与安全边界
+
+功能思路参考 Pengeek(米客)`OS4-26.10.13-test`,来源与许可在应用内「关于 → 第三方许可证与致谢」统一登记。实现独立编写,未复用参考模块或小米专有代码/资源;逆向材料仅保留在本地忽略的 `reverse_report/`。项目继续以 AGPL-3.0 分发。
+
+新增 Hook 仅修改 SystemUI 进程中的视图可见性与显示 Flow,不请求 Root、不执行 shell、不挂载或重新挂载分区、不操作块设备/AVB,也不向受保护系统分区写文件。安装 APK、项目既有偏好及系统/框架日志仍可能保存普通应用数据,因此此说明不承诺整个模块或临时 Root 环境的所有组件均无写入。
diff --git a/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/SystemUiLoad.kt b/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/SystemUiLoad.kt
index e18d986..a0fcfaf 100644
--- a/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/SystemUiLoad.kt
+++ b/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/SystemUiLoad.kt
@@ -11,6 +11,9 @@ import cn.ianzb.hyperrefine.hook.systemui.radius.CcRadiusHook
import cn.ianzb.hyperrefine.hook.systemui.radius.CcRadiusKeys
import cn.ianzb.hyperrefine.hook.systemui.wallpaper.WallpaperDarkDimHook
import cn.ianzb.hyperrefine.hook.systemui.wallpaper.WallpaperDimKeys
+import cn.ianzb.hyperrefine.hook.systemui.statusbar.HideWifiActivityHook
+import cn.ianzb.hyperrefine.hook.systemui.statusbar.MobileIndicatorVisibilityHook
+import cn.ianzb.hyperrefine.hook.systemui.statusbar.StatusBarKeys
/**
* 系统界面(com.android.systemui)目标 Load:
@@ -23,6 +26,8 @@ class SystemUiLoad : BaseLoad() {
override val targetPackages: List = listOf(TARGET_PACKAGE)
override fun onPackageLoaded(target: PackageTarget) {
+ initHook(HideWifiActivityHook(), HookPrefs.getBoolean(StatusBarKeys.WIFI_ACTIVITY))
+ initHook(MobileIndicatorVisibilityHook(), HookPrefs.getBoolean(StatusBarKeys.HIDE_TYPE))
initHook(CcVolumePercentHook(), HookPrefs.getBoolean(CcVolumePercentHook.KEY, false))
initHook(CcBrightnessPercentHook(), HookPrefs.getBoolean(CcBrightnessPercentHook.KEY, false))
initHook(SideVolumePercentHook(), HookPrefs.getBoolean(SideVolumePercentHook.KEY, false))
diff --git a/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/DualSimArtworkPolicy.kt b/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/DualSimArtworkPolicy.kt
new file mode 100644
index 0000000..9da9b0e
--- /dev/null
+++ b/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/DualSimArtworkPolicy.kt
@@ -0,0 +1,61 @@
+// DORMANT: preserved from statusbar.6 (ddb0e59); not compiled or registered.
+// Restore only after artwork and device regression testing. Runtime diagnostics removed.
+// package cn.ianzb.hyperrefine.hook.systemui.statusbar
+//
+// import kotlin.math.min
+// import kotlin.math.roundToInt
+//
+// /** Original four-cell artwork geometry and slot ordering, independent of the data SIM. */
+// internal object DualSimArtworkPolicy {
+// const val CELL_COUNT = 4
+// private const val INACTIVE_ALPHA = 0.28f
+//
+// data class Levels(val top: Int, val bottom: Int)
+// data class Cell(val row: Int, val column: Int, val left: Float, val top: Float,
+// val right: Float, val bottom: Float, val radius: Float)
+//
+// /** HyperOS has six native levels (0..5); round proportionally to four visible cells. */
+// fun fourCellLevel(nativeLevel: Int?): Int? = nativeLevel?.takeIf { it in 0..5 }
+// ?.let { (it * CELL_COUNT / 5f).roundToInt() }
+//
+// fun levels(primarySlot: Int, secondarySlot: Int, primaryLevel: Int?, secondaryLevel: Int?): Levels? {
+// val first = fourCellLevel(primaryLevel) ?: return null
+// val second = fourCellLevel(secondaryLevel) ?: return null
+// return when {
+// primarySlot == 0 && secondarySlot == 1 -> Levels(first, second)
+// primarySlot == 1 && secondarySlot == 0 -> Levels(second, first)
+// else -> null // Unknown/duplicate slots must not invent a SIM 1 / SIM 2 assignment.
+// }
+// }
+//
+// fun cells(width: Float, height: Float, thick: Boolean): List {
+// if (!width.isFinite() || !height.isFinite() || width <= 0f || height <= 0f) return emptyList()
+// val step = width / CELL_COUNT
+// val barWidth = step * if (thick) 0.80f else 0.64f
+// // Compact proportions independent of a tall Android signal drawable's intrinsic bounds.
+// val artworkHeight = min(height * 0.72f, width * 0.80f)
+// val artworkTop = height - artworkHeight
+// val bottomHeight = min(artworkHeight * 0.24f, barWidth * 0.92f)
+// val bottomTop = height - bottomHeight
+// val upperBaseline = bottomTop - artworkHeight * 0.09f
+// val upperHeight = upperBaseline - artworkTop
+// return (0 until CELL_COUNT).map { column ->
+// val left = column * step + (step - barWidth) / 2f
+// val barHeight = upperHeight * (0.4f + column * 0.2f)
+// val radius = min(barWidth, barHeight) * 0.48f
+// Cell(0, column, left, upperBaseline - barHeight, left + barWidth, upperBaseline, radius)
+// } + (0 until CELL_COUNT).map { column ->
+// val left = column * step + (step - barWidth) / 2f
+// val radius = min(barWidth, bottomHeight) * 0.48f
+// Cell(1, column, left, bottomTop, left + barWidth, height, radius)
+// }
+// }
+//
+// /** Preserve the host's RGB and alpha; inactive cells use the same hue at lower opacity. */
+// fun cellColor(foreground: Int, drawableAlpha: Int, filled: Boolean): Int {
+// val hostAlpha = foreground ushr 24
+// val strengthAlpha = if (filled) 1f else INACTIVE_ALPHA
+// val alpha = (hostAlpha * drawableAlpha.coerceIn(0, 255) / 255f * strengthAlpha).roundToInt()
+// return (foreground and 0x00ffffff) or (alpha shl 24)
+// }
+// }
diff --git a/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/DualSimRuntimeSupport.kt b/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/DualSimRuntimeSupport.kt
new file mode 100644
index 0000000..08785ad
--- /dev/null
+++ b/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/DualSimRuntimeSupport.kt
@@ -0,0 +1,58 @@
+// DORMANT: preserved from statusbar.6 (ddb0e59); not compiled or registered.
+// Restore only after artwork and device regression testing. Runtime diagnostics removed.
+// package cn.ianzb.hyperrefine.hook.systemui.statusbar
+//
+// import cn.ianzb.hyperrefine.hook.xposed.Reflect
+// import java.lang.reflect.Method
+// import java.lang.reflect.Modifier
+// import java.util.function.Consumer
+//
+// /** A later LoadedApk callback must not reinstall the same process's dual-SIM hooks. */
+// internal class DualSimInstallation {
+// private var installed = false
+//
+// @Synchronized
+// fun installOnce(install: () -> Unit): Boolean {
+// if (installed) return false
+// install() // A failed/rolled-back installation remains retryable.
+// installed = true
+// return true
+// }
+// }
+//
+// /** Collect with MobileUiAdapter's actual JavaAdapter, without the removed Dependency.get API. */
+// internal class DualSimFlowObserver {
+// private data class Collector(val owner: Any, val method: Method)
+// private data class Subscription(val flow: Any, val consumer: Consumer)
+// private var collector: Collector? = null
+// private val waiting = ArrayDeque()
+//
+// fun value(flow: Any): Any? = Reflect.callMethod(flow, "getValue")
+//
+// @Synchronized
+// fun attach(adapter: Any): Boolean {
+// if (collector?.owner === adapter) return false
+// check(collector == null) { "Dual-SIM JavaAdapter changed within one process" }
+// val method = StatusBarMembers.methods(adapter.javaClass, "alwaysCollectFlow") {
+// !Modifier.isStatic(it.modifiers) && it.parameterCount == 2 &&
+// !it.parameterTypes[0].isPrimitive && it.parameterTypes[1] == Consumer::class.java
+// }.single()
+// // Binding can precede MobileUiAdapter.start. Drain only after the real adapter is ready.
+// // Remove each subscription after success, so a failed drain does not lose pending work.
+// while (waiting.isNotEmpty()) {
+// val subscription = waiting.first()
+// method.invoke(adapter, subscription.flow, subscription.consumer)
+// waiting.removeFirst()
+// }
+// collector = Collector(adapter, method)
+// return true
+// }
+//
+// @Synchronized
+// fun observe(flow: Any, onValue: (Any?) -> Unit) {
+// val consumer = Consumer { onValue(it) }
+// val active = collector
+// if (active == null) waiting.addLast(Subscription(flow, consumer))
+// else active.method.invoke(active.owner, flow, consumer)
+// }
+// }
diff --git a/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/DualSimSignalHook.kt b/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/DualSimSignalHook.kt
new file mode 100644
index 0000000..f9fe916
--- /dev/null
+++ b/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/DualSimSignalHook.kt
@@ -0,0 +1,363 @@
+// DORMANT: preserved from statusbar.6 (ddb0e59); not compiled or registered.
+// Restore only after artwork and device regression testing. Runtime diagnostics removed.
+// package cn.ianzb.hyperrefine.hook.systemui.statusbar
+//
+// import android.graphics.Color
+// import android.graphics.Bitmap
+// import android.graphics.Canvas
+// import android.graphics.Rect
+// import android.graphics.drawable.Drawable
+// import android.os.Handler
+// import android.os.Looper
+// import android.view.View
+// import android.view.ViewGroup
+// import android.widget.ImageView
+// import cn.ianzb.hyperrefine.hook.base.BaseHook
+// import cn.ianzb.hyperrefine.hook.prefs.HookPrefs
+// import cn.ianzb.hyperrefine.hook.xposed.Reflect
+// import io.github.libxposed.api.XposedInterface.HookHandle
+// import java.lang.ref.WeakReference
+// import java.util.WeakHashMap
+// import java.util.concurrent.atomic.AtomicBoolean
+// import kotlin.math.roundToInt
+//
+// /** Event-driven native-view composition; no polling, telephony changes, or copied signal assets. */
+// class DualSimSignalHook : BaseHook() {
+// override val key = StatusBarKeys.DUAL_ROWS
+// override fun init() {
+// installation.installOnce { installFor(target.classLoader) }
+// }
+//
+// private fun installFor(loader: ClassLoader?) {
+// val binder = Reflect.findClass(StatusBarViews.MOBILE_BINDER, loader)
+// val binds = StatusBarMembers.methods(binder, "bind") {
+// it.parameterCount == 4 && ViewGroup::class.java.isAssignableFrom(it.parameterTypes[0]) &&
+// it.parameterTypes[1].name == "com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel" &&
+// it.parameterTypes[2].name == "com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MiuiMobileIconViewModel" &&
+// it.parameterTypes[3].name == "com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger"
+// }
+// val adapter = Reflect.findClass("com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter", loader)
+// val starts = StatusBarMembers.methods(adapter, "start") { it.parameterCount == 0 }
+// val collector = Reflect.findClass(StatusBarViews.MOBILE_BINDER + "\$bind\$1\$1\$4\$2", loader)
+// val icon = StatusBarMembers.field(collector, "\$mobile")
+// require(ImageView::class.java.isAssignableFrom(icon.type))
+// val emits = StatusBarMembers.methods(collector, "emit") {
+// it.parameterCount == 2 && it.parameterTypes[0] == Any::class.java &&
+// it.parameterTypes[1].name == "kotlin.coroutines.Continuation"
+// }
+// val themeCollector = Reflect.findClass(StatusBarViews.MOBILE_BINDER + "\$bind\$1\$1\$26\$2", loader)
+// val themeIcon = StatusBarMembers.field(themeCollector, "\$mobile")
+// require(ImageView::class.java.isAssignableFrom(themeIcon.type))
+// val themeEmits = StatusBarMembers.methods(themeCollector, "emit") {
+// it.parameterCount == 2 && it.parameterTypes[0] == Any::class.java &&
+// it.parameterTypes[1].name == "kotlin.coroutines.Continuation"
+// }
+// val tintCollector = Reflect.findClass(StatusBarViews.MOBILE_BINDER + "\$bind\$1\$1\$5\$1", loader)
+// val tintViews = StatusBarMembers.field(tintCollector, "\$tintViewList")
+// require(List::class.java.isAssignableFrom(tintViews.type))
+// val tintUpdates = StatusBarMembers.methods(tintCollector, "invokeSuspend") {
+// it.parameterTypes.contentEquals(arrayOf(Any::class.java)) && it.returnType == Any::class.java
+// }
+// val mobileView = Reflect.findClass("com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView", loader)
+// val states = StatusBarMembers.methods(mobileView, "setVisibleState") {
+// it.parameterTypes.contentEquals(arrayOf(Int::class.javaPrimitiveType, Boolean::class.javaPrimitiveType))
+// }
+// val runtime = DualSignalRuntime(DualSimFlowObserver())
+// val guard = StatusBarGuard(tag)
+// val installs = mutableListOf<() -> HookHandle>()
+// binds.forEach { method -> installs += { StatusBarMembers.after(method) { param -> guard.run {
+// val root = param.args.firstOrNull() as? ViewGroup ?: return@run
+// // bind(root, locationModel, miuiModel, logger): the location wrapper has no cell flows.
+// val model = param.args.getOrNull(2) ?: return@run
+// if (StatusBarViews.isStatusBarMobile(root, model)) runtime.bind(root, model)
+// } } } }
+// starts.forEach { method -> installs += { StatusBarMembers.after(method) { param -> guard.run {
+// param.thisObject?.let(runtime::adapter)
+// } } } }
+// emits.forEach { method -> installs += { StatusBarMembers.after(method) { param -> guard.run {
+// val instance = param.thisObject ?: return@run
+// val image = icon.get(instance) as? ImageView ?: return@run
+// runtime.emitted(image)
+// } } } }
+// themeEmits.forEach { method -> installs += { StatusBarMembers.after(method) { param -> guard.run {
+// val instance = param.thisObject ?: return@run
+// (themeIcon.get(instance) as? ImageView)?.let(runtime::emitted)
+// } } } }
+// tintUpdates.forEach { method -> installs += { StatusBarMembers.after(method) { param -> guard.run {
+// val instance = param.thisObject ?: return@run
+// (tintViews.get(instance) as? List<*>)?.filterIsInstance()?.forEach(runtime::emitted)
+// } } } }
+// states.forEach { method -> installs += { StatusBarMembers.after(method) { param ->
+// guard.run { (param.thisObject as? ViewGroup)?.let { runtime.visibleState(it, param.args[0] as Int) } }
+// } } }
+// StatusBarMembers.install(installs)
+// }
+//
+// private companion object {
+// val installation = DualSimInstallation()
+// }
+// }
+//
+// private class DualSignalRuntime(private val flows: DualSimFlowObserver) {
+// private class Row(root: ViewGroup, image: ImageView, val subId: Int) {
+// val root = WeakReference(root)
+// val image = WeakReference(image)
+// var native: Drawable? = null
+// var hiddenByModule = false
+// var nativeVisibility = root.visibility
+// var iconState = 0
+// var nativeColor = Color.WHITE
+// var renderKey: List? = null
+// val originalScaleX = image.scaleX
+// val originalScaleY = image.scaleY
+// val parent = WeakReference(image.parent as? ViewGroup)
+// val originalTranslation = (image.parent as? View)?.translationY ?: 0f
+// val originalClipChildren = (image.parent as? ViewGroup)?.clipChildren ?: true
+// val originalClipPadding = (image.parent as? ViewGroup)?.clipToPadding ?: true
+// val group = WeakReference(StatusBarViews.find(root, "mobile_group") as? ViewGroup)
+// val originalLeft = group.get()?.paddingLeft ?: 0
+// val originalRight = group.get()?.paddingRight ?: 0
+// var styled = false
+// }
+//
+// private val rows = WeakHashMap() // Accessed on the UI thread only.
+// private val observed = WeakHashMap()
+// private val signals = mutableMapOf()
+// private val slotIndexes = mutableMapOf()
+// private val signalLevels = mutableMapOf()
+// private var dataSubId = -1
+// private val handler = Handler(Looper.getMainLooper())
+// private val pending = AtomicBoolean()
+// private val guard = StatusBarGuard("DualSimSignalHook")
+//
+// fun adapter(adapter: Any) {
+// val hdController = Reflect.getObjectField(adapter, "hdController") ?: error("No mobile HD controller")
+// val javaAdapter = Reflect.getObjectField(hdController, "javaAdapter") ?: error("No mobile JavaAdapter")
+// flows.attach(javaAdapter)
+// val provider = Reflect.getObjectField(adapter, "mobileIconsViewModel") ?: error("No mobile model provider")
+// val model = Reflect.callMethod(provider, "get") ?: error("No mobile icons model")
+// val interactor = runCatching {
+// Reflect.callMethod(Reflect.getObjectField(model, "miuiIntsLazy")!!, "get")
+// }.getOrNull() ?: Reflect.getObjectField(model, "miuiInt") ?: error("No mobile interactor")
+// val repository = Reflect.getObjectField(interactor, "mobileConnectionsRepo") ?: error("No mobile repository")
+// val source = Reflect.getObjectField(repository, "activeMobileDataSubscriptionId") ?: error("No active SIM flow")
+// val initial = flows.value(source) as? Int ?: -1
+// synchronized(signals) { dataSubId = initial }
+// flows.observe(source) { value ->
+// (value as? Int)?.let { synchronized(signals) { dataSubId = it }; schedule() }
+// }
+// }
+//
+// fun bind(root: ViewGroup, model: Any) {
+// if (rows.containsKey(root)) return
+// val image = StatusBarViews.find(root, "mobile_signal") as? ImageView ?: error("No native signal image")
+// val subId = Reflect.getObjectField(root, "subId") as? Int ?: error("No SIM subscription id")
+// val cell = runCatching { Reflect.callMethod(model, "getCellProvider") }.getOrNull() ?: model
+// val signalFlow = Reflect.getObjectField(cell, "signalIconId") ?: error("No native signal flow")
+// val visibleFlow = runCatching { Reflect.getObjectField(model, "isVisible") }.getOrNull()
+// ?: Reflect.getObjectField(cell, "isVisible") ?: error("No native signal visibility")
+// val providerFlow = Reflect.getObjectField(model, "vmProvider") ?: error("No active mobile model flow")
+// val satelliteFlow = Reflect.getObjectField(cell, "satelliteVisible") ?: error("No satellite visibility flow")
+// val interactor = Reflect.getObjectField(model, "iconInteractor") ?: error("No SIM slot interactor")
+// val slotFlow = Reflect.getObjectField(interactor, "phoneId") ?: error("No SIM slot flow")
+// synchronized(signals) { slotIndexes.putIfAbsent(subId, flows.value(slotFlow) as? Int ?: -1) }
+// val resource = flows.value(signalFlow) as? Int ?: error("Unexpected signal flow value")
+// val visibility = StatusBarMembers.mobileVisibility(flows.value(visibleFlow)) ?: error("Unexpected visibility flow value")
+// val initial = StatusBarPolicy.Signal(subId, resource, visibility.signal, visibility.root,
+// flows.value(providerFlow) === cell,
+// flows.value(satelliteFlow) as? Boolean ?: error("Unexpected satellite visibility"))
+// val row = Row(root, image, subId)
+// capture(row, image)
+// if (!observed.containsKey(model)) {
+// flows.observe(signalFlow) { value -> guard.run {
+// val res = value as? Int ?: return@run
+// synchronized(signals) {
+// signals[subId] = (signals[subId] ?: initial).copy(resourceId = res)
+// }
+// schedule()
+// } }
+// flows.observe(visibleFlow) { value -> guard.run {
+// val shown = StatusBarMembers.mobileVisibility(value) ?: return@run
+// synchronized(signals) {
+// signals[subId] = (signals[subId] ?: initial).copy(visible = shown.signal, rootVisible = shown.root)
+// }
+// schedule()
+// } }
+// flows.observe(providerFlow) { value -> guard.run {
+// synchronized(signals) { signals[subId] = (signals[subId] ?: initial).copy(cellularMode = value === cell) }
+// schedule()
+// } }
+// flows.observe(satelliteFlow) { value -> guard.run {
+// val satellite = value as? Boolean ?: return@run
+// synchronized(signals) { signals[subId] = (signals[subId] ?: initial).copy(satellite = satellite) }
+// schedule()
+// } }
+// // A subscription ID is not a SIM slot; observe the host's actual slot mapping once per model.
+// flows.observe(slotFlow) { value ->
+// synchronized(signals) { slotIndexes[subId] = value as? Int ?: -1 }
+// schedule()
+// }
+// observed[model] = true
+// }
+// synchronized(signals) { signals.putIfAbsent(subId, initial) }
+// rows[root] = row
+// val listener = android.view.ViewTreeObserver.OnGlobalLayoutListener { schedule() }
+// var listening = false
+// fun attachListener() {
+// if (!listening && root.viewTreeObserver.isAlive) {
+// root.viewTreeObserver.addOnGlobalLayoutListener(listener); listening = true
+// }
+// }
+// root.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
+// override fun onViewAttachedToWindow(v: View) { attachListener(); schedule() }
+// override fun onViewDetachedFromWindow(v: View) {
+// if (v.viewTreeObserver.isAlive) v.viewTreeObserver.removeOnGlobalLayoutListener(listener)
+// listening = false
+// schedule()
+// }
+// })
+// if (root.isAttachedToWindow) attachListener()
+// schedule()
+// }
+//
+// fun visibleState(root: ViewGroup, state: Int) {
+// rows[root]?.let { it.iconState = state; schedule() }
+// }
+//
+// fun emitted(image: ImageView) {
+// val affected = rows.values.filter { it.image.get() === image }
+// if (affected.isEmpty()) return
+// affected.forEach { row -> capture(row, image) }
+// // A native emission may update the secondary theme drawable without changing its resource id.
+// rows.values.forEach { it.renderKey = null }
+// schedule()
+// }
+//
+// private fun capture(row: Row, image: ImageView) {
+// val native = image.drawable ?: return
+// if (native is StackedSignalDrawable) return
+// row.native = native.constantState?.newDrawable(image.resources)?.mutate()?.also {
+// it.colorFilter = native.colorFilter; it.state = native.state
+// }
+// row.native?.let { drawable -> row.nativeColor = sampleColor(drawable) }
+// }
+//
+// /** Read a tiny in-memory rendering, including the native tint, when a signal emission changes. */
+// private fun sampleColor(drawable: Drawable): Int {
+// val bitmap = Bitmap.createBitmap(24, 24, Bitmap.Config.ARGB_8888)
+// val original = Rect(drawable.bounds)
+// try {
+// drawable.setBounds(0, 0, 24, 24)
+// drawable.draw(Canvas(bitmap))
+// var best = -1f
+// var color = Color.WHITE
+// for (y in 0..23) for (x in 0..23) {
+// val pixel = bitmap.getPixel(x, y)
+// val brightness = (Color.red(pixel) + Color.green(pixel) + Color.blue(pixel)) / 3f
+// val score = Color.alpha(pixel) * kotlin.math.abs(brightness - 127.5f)
+// if (Color.alpha(pixel) > 0 && score > best) { best = score; color = pixel or (0xff shl 24) }
+// }
+// return color
+// } finally { drawable.bounds = original; bitmap.recycle() }
+// }
+//
+// private fun schedule() {
+// if (pending.compareAndSet(false, true)) handler.post {
+// pending.set(false)
+// guard.run { refresh() }
+// }
+// }
+//
+// private fun refresh() {
+// val snapshot = synchronized(signals) { Triple(signals.values.toList(), dataSubId, slotIndexes.toMap()) }
+// // Separate hosts (home / lock screen / another display) never share a composed view.
+// rows.values.toList().filter { it.root.get()?.isAttachedToWindow == true }
+// .groupBy { it.root.get()?.parent }.values.forEach { group ->
+// val eligible = group.filter {
+// it.iconState == 0 && it.image.get()?.visibility == View.VISIBLE &&
+// it.parent.get()?.visibility == View.VISIBLE
+// }.map { it.subId }.toSet()
+// val pairing = StatusBarPolicy.pair(snapshot.first.filter { it.subId in eligible }, snapshot.second)
+// val first = pairing?.let { pair -> group.firstOrNull { it.subId == pair.primary.subId } }
+// val second = pairing?.let { pair -> group.firstOrNull { it.subId == pair.secondary.subId } }
+// val levels = if (pairing != null && first != null && second != null) {
+// fun nativeLevel(row: Row, resource: Int): Int? {
+// if (!signalLevels.containsKey(resource)) signalLevels[resource] = runCatching {
+// StatusBarPolicy.signalLevel(row.image.get()!!.resources.getResourceEntryName(resource))
+// }.getOrNull()
+// return signalLevels[resource]
+// }
+// DualSimArtworkPolicy.levels(snapshot.third[first.subId] ?: -1, snapshot.third[second.subId] ?: -1,
+// nativeLevel(first, pairing.primary.resourceId), nativeLevel(second, pairing.secondary.resourceId))
+// } else null
+// if (pairing == null || first?.native == null || second?.native == null || levels == null) {
+// group.forEach { restore(it, snapshot.first.firstOrNull { state -> state.subId == it.subId }?.rootVisible) }
+// return@forEach
+// }
+// group.forEach { row ->
+// val root = row.root.get() ?: return@forEach
+// if (row === second) {
+// if (!row.hiddenByModule) { row.nativeVisibility = root.visibility; row.hiddenByModule = true }
+// if (root.visibility != View.GONE) root.visibility = View.GONE
+// } else if (row === first) {
+// restoreVisibility(row, true)
+// render(row, pairing, levels)
+// } else restore(row, snapshot.first.firstOrNull { it.subId == row.subId }?.rootVisible)
+// }
+// }
+// }
+//
+// private fun restoreVisibility(row: Row, visible: Boolean?) {
+// if (row.hiddenByModule) {
+// row.root.get()?.visibility = when (visible) {
+// true -> View.VISIBLE
+// false -> View.GONE
+// null -> row.nativeVisibility
+// }
+// row.hiddenByModule = false
+// }
+// }
+//
+// private fun restore(row: Row, visible: Boolean?) {
+// restoreVisibility(row, visible)
+// row.image.get()?.let { image ->
+// if (image.drawable is StackedSignalDrawable) row.native?.let(image::setImageDrawable)
+// if (row.styled) { image.scaleX = row.originalScaleX; image.scaleY = row.originalScaleY }
+// }
+// if (row.styled) {
+// row.parent.get()?.let { parent ->
+// parent.translationY = row.originalTranslation
+// parent.clipChildren = row.originalClipChildren; parent.clipToPadding = row.originalClipPadding
+// }
+// row.group.get()?.let { it.setPadding(row.originalLeft, it.paddingTop, row.originalRight, it.paddingBottom) }
+// row.styled = false
+// }
+// row.renderKey = null
+// }
+//
+// private fun render(first: Row, pair: StatusBarPolicy.Pairing, levels: DualSimArtworkPolicy.Levels) {
+// val image = first.image.get() ?: return
+// val root = first.root.get() ?: return
+// first.styled = true
+// val style = HookPrefs.getString(StatusBarKeys.DUAL_STYLE, "standard").takeIf { it in listOf("standard", "thick", "theme") } ?: "standard"
+// fun setting(key: String, default: Float, min: Float, max: Float) = StatusBarPolicy.bounded(HookPrefs.getFloat(key, default), default, min, max)
+// val scale = setting(StatusBarKeys.DUAL_SCALE, 1f, 0.6f, 1.6f)
+// val color = image.imageTintList?.getColorForState(image.drawableState, first.nativeColor) ?: first.nativeColor
+// val key = listOf(pair.primary.resourceId, pair.secondary.resourceId, levels.top, levels.bottom, style, color)
+// if (first.renderKey != key || image.drawable !is StackedSignalDrawable) {
+// image.setImageDrawable(StackedSignalDrawable(first.native!!, levels.top, levels.bottom, style, color))
+// first.renderKey = key
+// }
+// if (image.scaleX != scale) { image.scaleX = scale; image.scaleY = scale }
+// (image.parent as? ViewGroup)?.let { parent ->
+// parent.clipChildren = false; parent.clipToPadding = false
+// val offset = setting(StatusBarKeys.DUAL_OFFSET, 0f, -4f, 4f) * image.resources.displayMetrics.density
+// if (parent.translationY != offset) parent.translationY = offset
+// }
+// val group = StatusBarViews.find(root, "mobile_group") as? ViewGroup ?: return
+// val density = image.resources.displayMetrics.density
+// val left = (setting(StatusBarKeys.DUAL_LEFT_GAP, 0f, 0f, 8f) * density).roundToInt()
+// val right = (setting(StatusBarKeys.DUAL_RIGHT_GAP, 0f, 0f, 8f) * density).roundToInt()
+// if (group.paddingLeft != left || group.paddingRight != right) group.setPadding(left, group.paddingTop, right, group.paddingBottom)
+// }
+// }
diff --git a/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/MobileTypeHooks.kt b/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/MobileTypeHooks.kt
new file mode 100644
index 0000000..b84eb36
--- /dev/null
+++ b/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/MobileTypeHooks.kt
@@ -0,0 +1,28 @@
+package cn.ianzb.hyperrefine.hook.systemui.statusbar
+
+import cn.ianzb.hyperrefine.hook.base.BaseHook
+import cn.ianzb.hyperrefine.hook.xposed.Reflect
+
+class MobileIndicatorVisibilityHook : BaseHook() {
+ override val key = StatusBarKeys.HIDE_TYPE
+ override fun init() {
+ val loader = target.classLoader
+ val factory = Reflect.findClass("com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MiuiMobileIconVMImpl\$\$ExternalSyntheticLambda0", loader)
+ val cell = Reflect.findClass("com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MiuiCellularIconVM", loader)
+ val create = StatusBarMembers.methods(factory, "invoke") {
+ it.parameterCount == 0 && it.returnType == Any::class.java
+ }
+ // Native image, separate text, and special 5G visibility have independent collectors.
+ val visibility = listOf("mobileTypeVisible", "mobileTypeImageVisible", "mobileTypeSingleVisible", "showSpecial5GIcon")
+ .map { StatusBarMembers.field(cell, it) }
+ require(visibility.all { it.type.name == "kotlinx.coroutines.flow.ReadonlyStateFlow" })
+ val flows = StatusBarFlows(loader)
+ val hidden = flows.mutable(false)
+ val guard = StatusBarGuard(tag)
+ StatusBarMembers.install(create.map { method -> { StatusBarMembers.after(method) { param -> guard.run {
+ val model = param.result ?: return@run
+ require(cell.isInstance(model)) { "Unexpected cellular model" }
+ visibility.forEach { flows.replace(model, it, hidden) }
+ } } } })
+ }
+}
diff --git a/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/StackedSignalDrawable.kt b/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/StackedSignalDrawable.kt
new file mode 100644
index 0000000..6b7f8ce
--- /dev/null
+++ b/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/StackedSignalDrawable.kt
@@ -0,0 +1,65 @@
+// DORMANT: preserved from statusbar.6 (ddb0e59); not compiled or registered.
+// Restore only after artwork and device regression testing. Runtime diagnostics removed.
+// package cn.ianzb.hyperrefine.hook.systemui.statusbar
+//
+// import android.content.res.ColorStateList
+// import android.graphics.Canvas
+// import android.graphics.ColorFilter
+// import android.graphics.Paint
+// import android.graphics.PixelFormat
+// import android.graphics.drawable.Drawable
+//
+// /** Four independently drawn cells per SIM; SystemUI supplies the current light/dark tint. */
+// internal class StackedSignalDrawable(
+// private val nativeSizing: Drawable,
+// private val topLevel: Int,
+// private val bottomLevel: Int,
+// private val style: String,
+// private val baseColor: Int,
+// ) : Drawable() {
+// private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
+// private var tint: ColorStateList? = null
+// private var foreground = baseColor
+// private var opacity = 255
+// private var geometry: List = emptyList()
+// private var geometryWidth = -1
+// private var geometryHeight = -1
+//
+// override fun draw(canvas: Canvas) {
+// if (bounds.width() != geometryWidth || bounds.height() != geometryHeight) {
+// geometryWidth = bounds.width(); geometryHeight = bounds.height()
+// geometry = DualSimArtworkPolicy.cells(geometryWidth.toFloat(), geometryHeight.toFloat(), style == "thick")
+// }
+// geometry.forEach { cell ->
+// val level = if (cell.row == 0) topLevel else bottomLevel
+// paint.color = DualSimArtworkPolicy.cellColor(foreground, opacity, cell.column < level)
+// canvas.drawRoundRect(bounds.left + cell.left, bounds.top + cell.top,
+// bounds.left + cell.right, bounds.top + cell.bottom, cell.radius, cell.radius, paint)
+// }
+// }
+//
+// override fun setAlpha(alpha: Int) {
+// opacity = alpha.coerceIn(0, 255); nativeSizing.alpha = opacity; invalidateSelf()
+// }
+// override fun getAlpha() = opacity
+// override fun setColorFilter(filter: ColorFilter?) {
+// paint.colorFilter = filter; nativeSizing.colorFilter = filter; invalidateSelf()
+// }
+// override fun setTintList(tint: ColorStateList?) {
+// this.tint = tint
+// nativeSizing.setTintList(tint) // Keep the private native clone ready for single-SIM restoration.
+// foreground = tint?.getColorForState(state, tint.defaultColor) ?: baseColor
+// invalidateSelf()
+// }
+// override fun isStateful() = tint?.isStateful == true || nativeSizing.isStateful
+// override fun onStateChange(state: IntArray): Boolean {
+// val next = tint?.let { it.getColorForState(state, it.defaultColor) } ?: baseColor
+// val nativeChanged = nativeSizing.setState(state)
+// if (foreground == next) return nativeChanged
+// foreground = next; invalidateSelf(); return true
+// }
+// @Deprecated("Drawable opacity is retained for Android compatibility")
+// override fun getOpacity() = PixelFormat.TRANSLUCENT
+// override fun getIntrinsicWidth() = nativeSizing.intrinsicWidth
+// override fun getIntrinsicHeight() = nativeSizing.intrinsicHeight
+// }
diff --git a/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/StatusBarKeys.kt b/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/StatusBarKeys.kt
new file mode 100644
index 0000000..7ece9bd
--- /dev/null
+++ b/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/StatusBarKeys.kt
@@ -0,0 +1,16 @@
+package cn.ianzb.hyperrefine.hook.systemui.statusbar
+
+object StatusBarKeys {
+ const val TARGET = "com.android.systemui"
+ const val WIFI_ACTIVITY = "statusbar_wifi_hide_activity"
+ const val HIDE_TYPE = "statusbar_mobile_hide_type"
+ val ALL = listOf(WIFI_ACTIVITY, HIDE_TYPE)
+
+ // DORMANT: restore these only with the commented dual-SIM implementation and its UI.
+ // const val DUAL_ROWS = "statusbar_mobile_dual_rows"
+ // const val DUAL_STYLE = "statusbar_mobile_dual_style"
+ // const val DUAL_SCALE = "statusbar_mobile_dual_scale"
+ // const val DUAL_LEFT_GAP = "statusbar_mobile_dual_left_gap"
+ // const val DUAL_RIGHT_GAP = "statusbar_mobile_dual_right_gap"
+ // const val DUAL_OFFSET = "statusbar_mobile_dual_offset"
+}
diff --git a/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/StatusBarPolicy.kt b/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/StatusBarPolicy.kt
new file mode 100644
index 0000000..94af558
--- /dev/null
+++ b/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/StatusBarPolicy.kt
@@ -0,0 +1,29 @@
+// DORMANT: preserved from statusbar.6 (ddb0e59); not compiled or registered.
+// Restore only after artwork and device regression testing. Runtime diagnostics removed.
+// package cn.ianzb.hyperrefine.hook.systemui.statusbar
+//
+// internal object StatusBarPolicy {
+//
+// data class Visibility(val root: Boolean, val signal: Boolean)
+// data class Signal(
+// val subId: Int, val resourceId: Int, val visible: Boolean, val rootVisible: Boolean = visible,
+// val cellularMode: Boolean = true, val satellite: Boolean = false,
+// )
+// data class Pairing(val primary: Signal, val secondary: Signal)
+//
+// /** Only combine two real visible subscriptions. Unknown / single-SIM state keeps native layout. */
+// fun pair(signals: List, dataSubId: Int): Pairing? {
+// val visible = signals.filter { it.visible && it.cellularMode && !it.satellite && it.subId >= 0 && it.resourceId > 0 }
+// .distinctBy { it.subId }.sortedBy { it.subId }
+// if (visible.size != 2) return null
+// val primary = visible.firstOrNull { it.subId == dataSubId } ?: visible.first()
+// return Pairing(primary, visible.first { it.subId != primary.subId })
+// }
+//
+// private val levelName = Regex("^stat_sys_signal_([0-5])(?:_.*)?$")
+// fun signalLevel(resourceName: String): Int? =
+// if (resourceName == "stat_sys_signal_null") 0 else levelName.matchEntire(resourceName)?.groupValues?.get(1)?.toInt()
+//
+// fun bounded(value: Float, default: Float, min: Float, max: Float): Float =
+// if (value.isFinite()) value.coerceIn(min, max) else default
+// }
diff --git a/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/StatusBarSupport.kt b/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/StatusBarSupport.kt
new file mode 100644
index 0000000..d41b2a5
--- /dev/null
+++ b/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/StatusBarSupport.kt
@@ -0,0 +1,99 @@
+package cn.ianzb.hyperrefine.hook.systemui.statusbar
+
+import cn.ianzb.hyperrefine.hook.xposed.HookHelper
+import cn.ianzb.hyperrefine.hook.xposed.Reflect
+import cn.ianzb.hyperrefine.hook.xposed.HookParam
+import io.github.libxposed.api.XposedInterface.HookHandle
+import java.lang.reflect.Field
+import java.lang.reflect.Executable
+import java.lang.reflect.Method
+import java.util.concurrent.atomic.AtomicBoolean
+
+internal object StatusBarMembers {
+ fun after(executable: Executable, callback: (HookParam) -> Unit): HookHandle {
+ HookHelper.deoptimize(executable)
+ return HookHelper.hookAfter(executable, callback = callback)
+ }
+ fun field(type: Class<*>, name: String): Field {
+ var current: Class<*>? = type
+ while (current != null) {
+ current.declaredFields.firstOrNull { it.name == name }?.let {
+ return it.apply { isAccessible = true }
+ }
+ current = current.superclass
+ }
+ throw NoSuchFieldException(type.name + "#" + name)
+ }
+
+ fun methods(type: Class<*>, name: String, predicate: (Method) -> Boolean): List {
+ val result = linkedMapOf>, Method>()
+ var current: Class<*>? = type
+ while (current != null) {
+ current.declaredMethods.filter { it.name == name && predicate(it) }
+ .forEach { result.putIfAbsent(it.parameterTypes.toList(), it.apply { isAccessible = true }) }
+ current = current.superclass
+ }
+ check(result.isNotEmpty()) { type.name + "#" + name + ": no compatible method" }
+ return result.values.toList()
+ }
+
+ fun install(installers: List<() -> HookHandle>) {
+ val handles = mutableListOf()
+ try { installers.forEach { handles += it() } }
+ catch (error: Throwable) {
+ handles.asReversed().forEach { runCatching { it.unhook() } }
+ throw error
+ }
+ }
+
+ /** R8 can remove ReadonlyStateFlow's unused Job argument in the host APK. */
+ fun readonlyFlow(wrapper: Class<*>, flow: Any): Any {
+ val constructors = wrapper.declaredConstructors.filter {
+ it.parameterCount in 1..2 && it.parameterTypes[0].isInstance(flow) &&
+ (it.parameterCount == 1 || !it.parameterTypes[1].isPrimitive)
+ }
+ val constructor = constructors.singleOrNull { it.parameterCount == 1 }
+ ?: constructors.singleOrNull { it.parameterCount == 2 }
+ ?: throw NoSuchMethodException(wrapper.name + ": no compatible read-only flow constructor")
+ constructor.isAccessible = true
+ return if (constructor.parameterCount == 1) constructor.newInstance(flow)
+ else constructor.newInstance(flow, null)
+ }
+
+ // DORMANT: helper required when restoring DualSimSignalHook and StatusBarPolicy.
+ // The host Pair comes from its own Kotlin class loader; do not cast to the module's Pair.
+ // fun mobileVisibility(value: Any?): StatusBarPolicy.Visibility? {
+ // if (value is Boolean) return StatusBarPolicy.Visibility(value, value)
+ // if (value?.javaClass?.name != "kotlin.Pair") return null
+ // val root = Reflect.callMethod(value, "getFirst") as? Boolean ?: return null
+ // val signal = Reflect.callMethod(value, "getSecond") as? Boolean ?: return null
+ // return StatusBarPolicy.Visibility(root, root && signal)
+ // }
+}
+
+internal class StatusBarGuard(private val label: String) {
+ private val warned = AtomicBoolean()
+ fun run(block: () -> Unit) {
+ try { block() } catch (error: Throwable) {
+ if (warned.compareAndSet(false, true)) {
+ HookHelper.log(label + ": unsupported ROM member", error)
+ }
+ }
+ }
+}
+
+/** Use the host's Kotlin flow classes; no bundled coroutine dependency or polling. */
+internal class StatusBarFlows(private val loader: ClassLoader?) {
+ private val factory = Reflect.findClass("kotlinx.coroutines.flow.StateFlowKt", loader)
+ .getDeclaredMethod("MutableStateFlow", Any::class.java).apply { isAccessible = true }
+
+ fun mutable(value: Any): Any = checkNotNull(factory.invoke(null, value))
+ fun replace(owner: Any, field: Field, flow: Any) {
+ val assigned = if (field.type.isInstance(flow)) flow else {
+ val wrapper = Reflect.findClass("kotlinx.coroutines.flow.ReadonlyStateFlow", loader)
+ StatusBarMembers.readonlyFlow(wrapper, flow)
+ }
+ require(field.type.isInstance(assigned)) { field.name + ": unexpected flow type" }
+ field.set(owner, assigned)
+ }
+}
diff --git a/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/StatusBarViews.kt b/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/StatusBarViews.kt
new file mode 100644
index 0000000..ffdb6b8
--- /dev/null
+++ b/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/StatusBarViews.kt
@@ -0,0 +1,21 @@
+// DORMANT: preserved from statusbar.6 (ddb0e59); not compiled or registered.
+// Restore only after artwork and device regression testing. Runtime diagnostics removed.
+// package cn.ianzb.hyperrefine.hook.systemui.statusbar
+//
+// import android.view.View
+// import android.view.ViewGroup
+// import cn.ianzb.hyperrefine.hook.xposed.Reflect
+//
+// /** Native status-bar lookup only; no network-type styling or view reparenting. */
+// internal object StatusBarViews {
+// const val MOBILE_BINDER = "com.android.systemui.statusbar.pipeline.mobile.ui.binder.MiuiMobileIconBinder"
+//
+// fun find(root: View, name: String): View? {
+// val id = root.resources.getIdentifier(name, "id", StatusBarKeys.TARGET)
+// return if (id == 0) null else root.findViewById(id)
+// }
+//
+// fun isStatusBarMobile(root: ViewGroup, model: Any?): Boolean =
+// model?.javaClass?.name?.contains("ShadeCarrierGroup") != true &&
+// (runCatching { Reflect.getObjectField(root, "slot") }.getOrNull() in listOf("mobile", "demo_mobile"))
+// }
diff --git a/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/WifiIndicatorHooks.kt b/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/WifiIndicatorHooks.kt
new file mode 100644
index 0000000..cba6a4a
--- /dev/null
+++ b/hook/src/main/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/WifiIndicatorHooks.kt
@@ -0,0 +1,39 @@
+package cn.ianzb.hyperrefine.hook.systemui.statusbar
+
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import cn.ianzb.hyperrefine.hook.base.BaseHook
+import cn.ianzb.hyperrefine.hook.xposed.Reflect
+
+// Presentation-only override verified against HyperOS 4.0.22.0's native Wi-Fi binder.
+class HideWifiActivityHook : BaseHook() {
+ override val key = StatusBarKeys.WIFI_ACTIVITY
+ override fun init() {
+ val loader = target.classLoader
+ val name = "com.android.systemui.statusbar.pipeline.wifi.ui.binder.MiuiWifiViewBinder"
+ val binder = Reflect.findClass(name, loader)
+ val binding = Reflect.findClass(name + "\$bind\$2", loader)
+ val bindingActivity = StatusBarMembers.field(binding, "\$wifiActivity")
+ require(ImageView::class.java.isAssignableFrom(bindingActivity.type))
+ val bind = StatusBarMembers.methods(binder, "bind") {
+ it.parameterCount == 2 && it.parameterTypes[0] == ViewGroup::class.java &&
+ it.parameterTypes[1].name == "com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel" &&
+ binding.isAssignableFrom(it.returnType)
+ }
+ val collector = Reflect.findClass(name + "\$bind\$1\$1\$4\$1", loader)
+ val collectorActivity = StatusBarMembers.field(collector, "\$wifiActivity")
+ require(ImageView::class.java.isAssignableFrom(collectorActivity.type))
+ val update = StatusBarMembers.methods(collector, "invokeSuspend") {
+ it.parameterTypes.contentEquals(arrayOf(Any::class.java)) && it.returnType == Any::class.java
+ }
+ val guard = StatusBarGuard(tag)
+ val installs = bind.map { method -> { StatusBarMembers.after(method) { param -> guard.run {
+ param.result?.let { (bindingActivity.get(it) as ImageView).visibility = View.GONE }
+ } } } } + update.map { method -> { StatusBarMembers.after(method) { param -> guard.run {
+ // Native activity updates force this view visible even for the no-traffic drawable.
+ param.thisObject?.let { (collectorActivity.get(it) as ImageView).visibility = View.GONE }
+ } } } }
+ StatusBarMembers.install(installs)
+ }
+}
diff --git a/hook/src/test/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/DualSimArtworkPolicyTest.kt b/hook/src/test/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/DualSimArtworkPolicyTest.kt
new file mode 100644
index 0000000..c623a62
--- /dev/null
+++ b/hook/src/test/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/DualSimArtworkPolicyTest.kt
@@ -0,0 +1,84 @@
+// DORMANT: preserved from statusbar.6 (ddb0e59); not compiled or registered.
+// Restore only after artwork and device regression testing. Runtime diagnostics removed.
+// package cn.ianzb.hyperrefine.hook.systemui.statusbar
+//
+// import org.junit.Assert.*
+// import org.junit.Test
+//
+// class DualSimArtworkPolicyTest {
+// @Test fun sixNativeStrengthLevelsRemainMonotonicWithinFourCells() {
+// assertEquals(listOf(0, 1, 2, 2, 3, 4), (0..5).map(DualSimArtworkPolicy::fourCellLevel))
+// assertNull(DualSimArtworkPolicy.fourCellLevel(null))
+// assertNull(DualSimArtworkPolicy.fourCellLevel(-1))
+// assertNull(DualSimArtworkPolicy.fourCellLevel(6))
+// }
+//
+// @Test fun dataSimSwitchDoesNotSwapTheDisplayedPhysicalSimRows() {
+// val fromSim1 = DualSimArtworkPolicy.levels(0, 1, 5, 2)
+// val fromSim2 = DualSimArtworkPolicy.levels(1, 0, 2, 5)
+// assertEquals(DualSimArtworkPolicy.Levels(4, 2), fromSim1)
+// assertEquals(fromSim1, fromSim2)
+// }
+//
+// @Test fun duplicateOrUnknownSlotsAndUnknownStrengthKeepNativeLayout() {
+// assertNull(DualSimArtworkPolicy.levels(0, 0, 3, 4))
+// assertNull(DualSimArtworkPolicy.levels(-1, 1, 3, 4))
+// assertNull(DualSimArtworkPolicy.levels(0, 2, 3, 4))
+// assertNull(DualSimArtworkPolicy.levels(0, 1, null, 4))
+// }
+//
+// @Test fun bothWidthsHaveFourAlignedCellsPerRowInsideTheDrawable() {
+// for (thick in listOf(false, true)) for ((width, height) in listOf(16f to 20f, 32f to 28f, 8f to 8f)) {
+// val cells = DualSimArtworkPolicy.cells(width, height, thick)
+// assertEquals(8, cells.size)
+// assertEquals(listOf(0, 1, 2, 3), cells.filter { it.row == 0 }.map { it.column })
+// cells.forEach { assertTrue(it.left >= 0 && it.top >= -0.0001f && it.right <= width && it.bottom <= height) }
+// cells.take(4).zip(cells.drop(4)).forEach { (upper, lower) ->
+// assertEquals(upper.left, lower.left, 0f)
+// assertEquals(upper.right, lower.right, 0f)
+// assertTrue(upper.bottom < lower.top)
+// }
+// }
+// }
+//
+// @Test fun upperRowRisesAndLowerRowHasEqualShortCells() {
+// val cells = DualSimArtworkPolicy.cells(16f, 20f, false)
+// val upper = cells.take(4)
+// val lower = cells.drop(4)
+// assertTrue(upper.zipWithNext().all { (a, b) -> a.top > b.top })
+// assertEquals(1, upper.map { it.bottom }.distinct().size)
+// assertEquals(1, lower.map { it.top }.distinct().size)
+// assertEquals(1, lower.map { it.bottom }.distinct().size)
+// assertTrue(lower.first().bottom - lower.first().top < upper.first().bottom - upper.first().top)
+// // Both rows fit a compact silhouette rather than filling the host's tall drawable bounds.
+// assertTrue(lower.last().bottom - upper.last().top < 16f)
+// cells.forEach {
+// val shortSide = minOf(it.right - it.left, it.bottom - it.top)
+// assertTrue(it.radius > shortSide * 0.4f)
+// assertTrue(it.radius <= shortSide / 2f)
+// }
+// }
+//
+// @Test fun invalidBoundsDoNotProduceInvalidCoordinates() {
+// assertTrue(DualSimArtworkPolicy.cells(0f, 20f, false).isEmpty())
+// assertTrue(DualSimArtworkPolicy.cells(16f, -1f, false).isEmpty())
+// assertTrue(DualSimArtworkPolicy.cells(Float.NaN, 20f, false).isEmpty())
+// }
+//
+// @Test fun whiteAndBlackHostPalettesRetainRgbAndDimOnlyInactiveCells() {
+// for (color in listOf(0xffffffff.toInt(), 0xff000000.toInt(), 0xff75aaff.toInt())) {
+// assertEquals(color, DualSimArtworkPolicy.cellColor(color, 255, true))
+// val inactive = DualSimArtworkPolicy.cellColor(color, 255, false)
+// assertEquals(color and 0x00ffffff, inactive and 0x00ffffff)
+// assertEquals(71, inactive ushr 24)
+// }
+// }
+//
+// @Test fun hostTransparencyAndDrawableFadeMultiplyForBothSignalStates() {
+// val color = 0x80ffffff.toInt()
+// assertEquals(64, DualSimArtworkPolicy.cellColor(color, 128, true) ushr 24)
+// assertEquals(18, DualSimArtworkPolicy.cellColor(color, 128, false) ushr 24)
+// assertEquals(0, DualSimArtworkPolicy.cellColor(color, 0, true) ushr 24)
+// assertEquals(128, DualSimArtworkPolicy.cellColor(color, 999, true) ushr 24)
+// }
+// }
diff --git a/hook/src/test/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/DualSimRuntimeSupportTest.kt b/hook/src/test/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/DualSimRuntimeSupportTest.kt
new file mode 100644
index 0000000..966f020
--- /dev/null
+++ b/hook/src/test/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/DualSimRuntimeSupportTest.kt
@@ -0,0 +1,96 @@
+// DORMANT: preserved from statusbar.6 (ddb0e59); not compiled or registered.
+// Restore only after artwork and device regression testing. Runtime diagnostics removed.
+// package cn.ianzb.hyperrefine.hook.systemui.statusbar
+//
+// import org.junit.Assert.*
+// import org.junit.Test
+// import java.lang.reflect.InvocationTargetException
+// import java.util.function.Consumer
+//
+// class DualSimRuntimeSupportTest {
+// class Flow(var current: Any?) {
+// val listeners = mutableListOf>()
+// fun getValue(): Any? = current
+// fun emit(value: Any?) { current = value; listeners.forEach { it.accept(value) } }
+// }
+//
+// class Adapter {
+// var failNext = false
+// fun alwaysCollectFlow(flow: Flow, consumer: Consumer) {
+// if (failNext) { failNext = false; error("collector not ready") }
+// flow.listeners += consumer
+// consumer.accept(flow.current)
+// }
+// }
+//
+// @Test fun bindBeforeAdapterPreservesInitialAndLaterSignalEvents() {
+// val observer = DualSimFlowObserver()
+// val signal = Flow(2)
+// val seen = mutableListOf()
+// observer.observe(signal) { seen += it }
+// signal.emit(3)
+// assertTrue(seen.isEmpty())
+// assertTrue(observer.attach(Adapter()))
+// signal.emit(4)
+// assertEquals(listOf(3, 4), seen)
+// assertEquals(4, observer.value(signal))
+// }
+//
+// @Test fun adapterBeforeBindCollectsIndependentFlowsAndDoesNotSubscribeTwice() {
+// val observer = DualSimFlowObserver()
+// val adapter = Adapter()
+// assertTrue(observer.attach(adapter))
+// val first = Flow(1)
+// val second = Flow(2)
+// val firstSeen = mutableListOf()
+// val secondSeen = mutableListOf()
+// observer.observe(first) { firstSeen += it }
+// observer.observe(second) { secondSeen += it }
+// assertFalse(observer.attach(adapter))
+// first.emit(3)
+// second.emit(4)
+// assertEquals(listOf(1, 3), firstSeen)
+// assertEquals(listOf(2, 4), secondSeen)
+// assertEquals(1, first.listeners.size)
+// assertEquals(1, second.listeners.size)
+// }
+//
+// @Test fun failedDrainRetainsPendingSubscriptionForRetry() {
+// val observer = DualSimFlowObserver()
+// val adapter = Adapter().apply { failNext = true }
+// val signal = Flow(5)
+// val seen = mutableListOf()
+// observer.observe(signal) { seen += it }
+// try { observer.attach(adapter); fail("Must expose collection failure") }
+// catch (error: InvocationTargetException) { assertEquals("collector not ready", error.cause?.message) }
+// assertTrue(observer.attach(adapter))
+// assertEquals(listOf(5), seen)
+// assertEquals(1, signal.listeners.size)
+// }
+//
+// @Test fun unrelatedAdapterCannotConsumeOrDropPendingSignals() {
+// val observer = DualSimFlowObserver()
+// val seen = mutableListOf()
+// observer.observe(Flow(1)) { seen += it }
+// try { observer.attach(Any()); fail("Must reject an unsupported adapter") }
+// catch (_: IllegalStateException) { assertTrue(seen.isEmpty()) }
+// observer.attach(Adapter())
+// assertEquals(listOf(1), seen)
+// }
+//
+// @Test fun laterLoadedApkCallbacksReuseOnlyCompletedInstallation() {
+// val installation = DualSimInstallation()
+// var calls = 0
+// assertTrue(installation.installOnce { calls++ })
+// assertFalse(installation.installOnce { error("Should not resolve classes from another loader") })
+// assertEquals(1, calls)
+// }
+//
+// @Test fun failedInstallationKeepsOriginalErrorAndAllowsRealRetry() {
+// val installation = DualSimInstallation()
+// val expected = ClassNotFoundException("binder")
+// try { installation.installOnce { throw expected }; fail("Must not record failed installation") }
+// catch (actual: ClassNotFoundException) { assertSame(expected, actual) }
+// assertTrue(installation.installOnce {})
+// }
+// }
diff --git a/hook/src/test/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/StatusBarPolicyTest.kt b/hook/src/test/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/StatusBarPolicyTest.kt
new file mode 100644
index 0000000..1b28cbb
--- /dev/null
+++ b/hook/src/test/java/cn/ianzb/hyperrefine/hook/systemui/statusbar/StatusBarPolicyTest.kt
@@ -0,0 +1,53 @@
+package cn.ianzb.hyperrefine.hook.systemui.statusbar
+
+import org.junit.Assert.*
+import org.junit.Test
+
+class StatusBarPolicyTest {
+ @Test fun retainedOptionsHaveUniqueKeysAndOnlySystemUiIsTargeted() {
+ assertEquals(listOf(StatusBarKeys.WIFI_ACTIVITY, StatusBarKeys.HIDE_TYPE), StatusBarKeys.ALL)
+ assertEquals(2, StatusBarKeys.ALL.distinct().size)
+ assertEquals("com.android.systemui", StatusBarKeys.TARGET)
+ }
+
+ open class Parent { private var field = 1; open fun build(value: String) {}; fun build(value: Int) {} }
+ class Child : Parent() { override fun build(value: String) {} }
+ @Test fun memberResolutionValidatesSignaturesAndSupportsInheritedPrivateFields() {
+ val child = Child()
+ assertEquals(1, StatusBarMembers.field(Child::class.java, "field").getInt(child))
+ val methods = StatusBarMembers.methods(Child::class.java, "build") {
+ it.parameterTypes.contentEquals(arrayOf(String::class.java)) && it.returnType == Void.TYPE
+ }
+ assertEquals(1, methods.size)
+ assertEquals(Child::class.java, methods.single().declaringClass)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun missingCompatibleHookMethodIsRejected() {
+ StatusBarMembers.methods(Child::class.java, "build") { it.parameterTypes.contentEquals(arrayOf(Boolean::class.javaPrimitiveType)) }
+ }
+
+ class MutableFlow(val value: Boolean)
+ class OptimizedReadonly private constructor(val flow: MutableFlow)
+ class UnoptimizedReadonly(val flow: MutableFlow, val job: Any?)
+ class InvalidReadonly(val value: Int)
+
+ @Test fun romOptimizedReadonlyFlowConstructorRetainsTheReplacementFlow() {
+ val source = MutableFlow(false)
+ val wrapped = StatusBarMembers.readonlyFlow(OptimizedReadonly::class.java, source) as OptimizedReadonly
+ assertSame(source, wrapped.flow)
+ assertFalse(wrapped.flow.value)
+ }
+
+ @Test fun unoptimizedReadonlyFlowConstructorRemainsSupported() {
+ val source = MutableFlow(false)
+ val wrapped = StatusBarMembers.readonlyFlow(UnoptimizedReadonly::class.java, source) as UnoptimizedReadonly
+ assertSame(source, wrapped.flow)
+ assertNull(wrapped.job)
+ }
+
+ @Test(expected = NoSuchMethodException::class)
+ fun unknownReadonlyFlowSignatureIsRejectedInsteadOfConstructingInvalidState() {
+ StatusBarMembers.readonlyFlow(InvalidReadonly::class.java, MutableFlow(false))
+ }
+}
|