Android16 状态栏蓝牙图标显示方案修改笔记
RK3576 Android16 状态栏蓝牙图标显示方案笔记
适用平台:Rockchip RK3576 + Android 16(AOSP 16)
项目路径:~aosp\frameworks\base\packages\SystemUI
SystemUI 模块路径:~aosp\frameworks\base\packages\SystemUI
一、需求背景
原始现象:状态栏不显示蓝牙图标,无论蓝牙是否开启、是否已连接设备。
期望行为:状态栏蓝牙图标跟随系统蓝牙状态显示——只要蓝牙开启就显示图标;同时保留 edla 测试模式下的原有行为(仅连接设备且满足音频条件时显示)。
最终方案:在 PhoneStatusBarPolicy.updateBluetooth() 中以系统属性 persist.sys.edla_test 为开关,区分两套显示逻辑。
二、状态栏蓝牙图标加载链路(布局加载逻辑)
状态栏蓝牙图标的显示涉及多个组件协作,链路如下:
2.1 图标 slot 名来源
- 文件:PhoneStatusBarPolicy.java
- 行号:L230
- 代码:
mSlotBluetooth = resources.getString(com.android.internal.R.string.status_bar_bluetooth); - 说明:bluetooth slot 名来自 framework internal 资源
com.android.internal.R.string.status_bar_bluetooth,值为字符串"bluetooth"。SystemUI 本身 res 中不定义该字符串。
2.2 图标控制器(StatusBarIconController)
- 接口:StatusBarIconController.java
- 实现:StatusBarIconControllerImpl.java
- 关键方法:
setIcon(slot, iconId, contentDescription):设置图标资源(L231-L239),首次调用会创建StatusBarIconHolder并加入mStatusBarIconListsetIconVisibility(slot, visibility):L384-L392public void setIconVisibility(String slot, boolean visibility, int tag) { StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, tag); if (holder == null || holder.isVisible() == visibility) { return; } holder.setVisible(visibility); handleSet(slot, holder); }- 注意:若
holder == null(未先setIcon)则直接 return,可见性设置无效。PhoneStatusBarPolicy.updateBluetooth()中先setIcon再setIconVisibility,链路完整。
2.3 图标视图(StatusBarIconView)
- 文件:StatusBarIconView.java
- 关键字段:
private final boolean mBlocked;(final,只在构造器赋值一次) set(StatusBarIcon icon)方法(L418-L420):setVisibility(icon.visible && !mBlocked ? VISIBLE : GONE);- 决定可见性的两个因子:
icon.visible:由setIconVisibility()设置mBlocked:构造时通过参数注入,运行时无法修改
2.4 图标管理器(IconManager)
- 文件:IconManager.java
- 关键方法
addHolder(L170-L186):boolean blocked = false; if (mBlockList.contains(slot)) { blocked = true; } mBlockList来源:setBlockList(List<String>)(L146-L153),由CollapsedStatusBarFragment.updateBlockedIcons()从 config 资源加载并传入。onCreateStatusBarIconView(slot, blocked)(L245-L247):用 blocked 标志构造StatusBarIconView。
2.5 图标容器(StatusIconContainer)
- 文件:StatusIconContainer.java
- 屏蔽机制:
mIgnoredSlots(L77)addIgnoredSlot(slot):加入忽略列表- 在
onMeasure/onLayout中对 ignored slot 强制设为STATE_HIDDEN(L339-L344),即使icon.visible=true也不显示
- 本项目中 bluetooth slot 未被任何代码
addIgnoredSlot。
2.6 状态栏 Fragment(CollapsedStatusBarFragment)
- 文件:CollapsedStatusBarFragment.java
updateBlockedIcons()(L440-L465):String[] blockList = getResources().getStringArray( R.array.config_collapsed_statusbar_icon_blocklist); // ... 仅对 status_bar_volume 做特殊处理 mDarkIconManager.setBlockList(mBlockedIcons);- 在
onViewCreated(L398)调用。
2.7 蓝牙状态回调链路
BluetoothController(接口) +BluetoothControllerImpl(实现):BluetoothControllerImpl.java- 状态字段:
mEnabled = (bluetoothState == BluetoothAdapter.STATE_ON || BluetoothAdapter.STATE_TURNING_ON)(L339) - 关键方法:
isBluetoothEnabled()→ 返回mEnabledisBluetoothConnected()→ 蓝牙是否已连接设备isBluetoothAudioActive()/isBluetoothAudioProfileOnly()→ 音频状态判断
- 回调注册:
PhoneStatusBarPolicy在init()中mBluetooth.addCallback(this)(L352),监听:onBluetoothStateChange(boolean enabled)→ 调updateBluetooth()onBluetoothDevicesChanged()→ 调updateBluetooth()
2.8 链路总览
BluetoothControllerImpl (蓝牙状态变化)
│ callback
▼
PhoneStatusBarPolicy.updateBluetooth()
│ mIconController.setIcon / setIconVisibility
▼
StatusBarIconControllerImpl (holder.setVisible)
│ handleSet → mIconGroups.forEach(onSetIcon)
▼
IconManager (addHolder, 检查 mBlockList 决定 blocked)
│ onCreateStatusBarIconView(slot, blocked)
▼
StatusBarIconView (icon.visible && !mBlocked ? VISIBLE : GONE)
│
▼
StatusIconContainer (检查 mIgnoredSlots, 决定 STATE_HIDDEN / STATE_ICON)
│
▼
状态栏最终显示
三、屏蔽机制排查(已全部排除)
经彻底排查,本项目不存在主动屏蔽状态栏 bluetooth 图标的代码路径。所有可能的屏蔽机制均不含 bluetooth:
| 屏蔽机制 | 配置/位置 | 是否屏蔽 bluetooth |
|---|---|---|
config_collapsed_statusbar_icon_blocklist |
config.xml L935-L939 | 否(仅 volume/alarm_clock/call_strength) |
config_keyguard_statusbar_icon_blocklist |
config.xml L942-L945 | 否(同上) |
config_statusBarIconsToExclude |
config.xml L565-L568 | 否(仅 rotate/headset) |
StatusBarPipelineFlags.isIconControlledByFlags |
StatusBarPipelineFlags.kt L50-L51 | 否(仅 wifi/mobile) |
BindableIconsRegistry 注册的 BindableIcon |
BindableIconsRegistry.kt | 否(仅 oemSatellite/stackedMobile) |
StatusBarIconView.mBlocked |
StatusBarIconView.java L150, L193, L418-L420 | 否(源自 IconManager.mBlockList,由上述 config 决定) |
StatusIconContainer.mIgnoredSlots |
StatusIconContainer.java L77, L257, L339-L344 | 否(只有 carrier/camera/mic/location 会被加入) |
persist.sys...bluetooth_enable |
BluetoothTile.java L133, L176, L185 | 否(仅屏蔽 QS 磁贴点击/长按,不影响状态栏图标可见性) |
关键结论
AOSP 默认 updateBluetooth() 逻辑要求蓝牙已连接设备且满足音频条件才显示图标:
// AOSP 原逻辑
if (mBluetooth.isBluetoothConnected()
&& (mBluetooth.isBluetoothAudioActive()
|| !mBluetooth.isBluetoothAudioProfileOnly())) {
bluetoothVisible = mBluetooth.isBluetoothEnabled();
}
因此"蓝牙开启但未连接设备"或"连接但音频条件不满足"时,状态栏蓝牙图标不显示——这是 AOSP 默认行为,并非项目自定义屏蔽。
四、修改方案
4.1 修改文件清单
仅修改一个文件:
| 文件 | 修改点 |
|---|---|
| PhoneStatusBarPolicy.java | 1. 添加 SystemProperties import2. 重写 updateBluetooth() 方法 |
4.2 修改点 1:添加 import
位置:PhoneStatusBarPolicy.java L37
修改前:
import android.os.RemoteException;
import android.os.UserManager;
修改后:
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserManager;
4.3 修改点 2:重写 updateBluetooth()
位置:PhoneStatusBarPolicy.java L523-L562
修改后完整代码:
private final void updateBluetooth() {
int iconId = R.drawable.stat_sys_data_bluetooth_connected;
String contentDescription =
mResources.getString(R.string.accessibility_quick_settings_bluetooth_on);
boolean bluetoothVisible = false;
boolean btEnabled = mBluetooth != null && mBluetooth.isBluetoothEnabled();
boolean btConnected = mBluetooth != null && mBluetooth.isBluetoothConnected();
boolean isEdlaTest = SystemProperties.getBoolean("persist.sys.edla_test", false);
if (isEdlaTest) {
// edla 测试模式:原有逻辑,蓝牙连接且满足音频条件才显示
if (mBluetooth != null && btConnected
&& (mBluetooth.isBluetoothAudioActive()
|| !mBluetooth.isBluetoothAudioProfileOnly())) {
contentDescription = mResources.getString(
R.string.accessibility_bluetooth_connected);
bluetoothVisible = btEnabled;
}
} else {
// 非 edla 测试模式:蓝牙开启即显示
if (btEnabled) {
bluetoothVisible = true;
if (btConnected
&& (mBluetooth.isBluetoothAudioActive()
|| !mBluetooth.isBluetoothAudioProfileOnly())) {
contentDescription = mResources.getString(
R.string.accessibility_bluetooth_connected);
}
}
}
if (DEBUG){
Log.d("pdd", "updateBluetooth: isEdlaTest=" + isEdlaTest
+ " enabled=" + btEnabled
+ " connected=" + btConnected
+ " visible=" + bluetoothVisible);
}
mIconController.setIcon(mSlotBluetooth, iconId, contentDescription);
mIconController.setIconVisibility(mSlotBluetooth, bluetoothVisible);
}
4.4 分支逻辑说明
isEdlaTest = true(edla 测试模式):保留 AOSP 原有逻辑。仅当蓝牙已连接设备且满足音频条件(isBluetoothAudioActive() || !isBluetoothAudioProfileOnly())时才显示图标,避免测试环境状态栏出现额外图标干扰。isEdlaTest = false(非 edla 测试模式 / 正式出厂模式):只要蓝牙开启(isBluetoothEnabled())就显示图标,符合"状态栏蓝牙图标跟随系统蓝牙状态显示"的需求。原音频条件仅用于切换到"已连接"的无障碍描述文案。
4.5 行为对照表
| 系统蓝牙状态 | edla 测试模式 (isEdlaTest=true) |
非测试模式 (isEdlaTest=false) |
|---|---|---|
| 蓝牙关闭 | 不显示 | 不显示 |
| 蓝牙开启 + 未连接设备 | 不显示 | 显示 |
| 蓝牙开启 + 连接 + 满足音频条件 | 显示(已连接描述) | 显示(已连接描述) |
| 蓝牙开启 + 连接 + 不满足音频条件 | 不显示 | 显示(普通描述) |
五、验证方法
5.1 编译部署
源码修改后必须重新编译 SystemUI 模块并部署到设备:
# 编译 SystemUI 模块
mmma SystemUI
# 部署到设备
adb root
adb remount
adb push SystemUI.apk /system/priv-app/SystemUI/
# 重启 SystemUI 进程使修改生效
adb shell killall com.android.systemui
5.2 日志验证
项目惯例使用 Log.d("pdd", ...) 作为状态栏/图标可见性相关调试 tag:
adb logcat -s pdd
切换蓝牙开关时预期输出类似:
D pdd: updateBluetooth: isEdlaTest=false enabled=true connected=true visible=true
5.3 系统属性切换
# 查看当前 edla 测试模式状态
adb shell getprop persist.sys.edla_test
# 切换到 edla 测试模式(走原逻辑)
adb shell setprop persist.sys.edla_test true
# 切换到非测试模式(走修改后逻辑,蓝牙开启即显示)
adb shell setprop persist.sys.edla_test false
切换后开关一次蓝牙(或重启 SystemUI)以触发 updateBluetooth() 重新计算。
5.4 根因诊断指引
根据 adb logcat -s pdd 输出判断:
| 日志现象 | 可能根因 | 下一步 |
|---|---|---|
无任何 updateBluetooth 输出 |
mBluetooth.addCallback(this) 未执行 / 回调链路断 |
检查 PhoneStatusBarPolicy.init() 是否被调用 |
enabled=false 但蓝牙实际开启 |
BluetoothControllerImpl.mEnabled 未更新 |
检查 onBluetoothStateChanged 回调 |
enabled=true visible=true 但图标仍不显示 |
IconManager 视图创建层问题 | 检查 StatusBarIconControllerImpl.setIcon 是否成功创建 holder |
| 日志正常且图标显示 | 修改已生效 | 完成 |
六、项目惯例与注意事项
6.1 系统属性屏蔽模式
本项目使用 persist.sys.* 系列系统属性控制行为,已知:
| 属性 | 作用 | 文件 |
|---|---|---|
persist.sys.edla_test |
edla 测试模式开关(影响屏幕录制指示器、状态栏蓝牙图标显示逻辑) | MediaProjectionPrivacyItemMonitor.kt、ScreenRecordChipInteractor.kt、PhoneStatusBarPolicy.java |
persist.sys...bluetooth_enable |
QS 蓝牙磁贴点击开关(不影响状态栏图标) | BluetoothTile.java |
persist.sys...wifi_enable |
QS Wi-Fi 磁贴点击开关 | QSTileImpl.java |
6.2 调试 Tag 约定
状态栏/图标可见性相关调试统一使用 tag pdd:
Log.d("pdd", "..."); // 通过 adb logcat -s pdd 查看
6.3 修改原则
- 本方案仅修改
PhoneStatusBarPolicy.updateBluetooth()一个方法 + 一个 import,不触碰布局 XML、不动 config 资源、不新增自定义 drawable。 - 避免重蹈覆辙:此前项目曾尝试自定义
bouncer_password_edittext_background.xml导致 XML 解析错误和 system_server 崩溃,故本次修改尽量避免资源层改动。
七、参考文件路径速查
| 类别 | 文件 |
|---|---|
| 修改文件 | src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java |
| 图标 slot 定义 | src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java L230 |
| 图标控制器接口 | src/com/android/systemui/statusbar/phone/ui/StatusBarIconController.java |
| 图标控制器实现 | src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java |
| 图标视图 | src/com/android/systemui/statusbar/StatusBarIconView.java |
| 图标管理器 | src/com/android/systemui/statusbar/phone/ui/IconManager.java |
| 图标容器 | src/com/android/systemui/statusbar/phone/StatusIconContainer.java |
| 状态栏 Fragment | src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java |
| 蓝牙状态控制器 | src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java |
| Pipeline Flags | src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt |
| Bindable 图标注册 | src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt |
| 配置资源 | res/values/config.xml |
| QS 蓝牙磁贴 | src/com/android/systemui/qs/tiles/BluetoothTile.java |
八、变更摘要
| 项 | 内容 |
|---|---|
| 平台 | RK3576 + Android 16 |
| 模块 | SystemUI |
| 修改文件数 | 1 |
| 修改方法数 | 1(updateBluetooth) |
| 新增 import | 1(android.os.SystemProperties) |
| 行为开关 | persist.sys.edla_test(true=原逻辑 / false=跟随系统蓝牙状态) |
| 调试 tag | pdd(adb logcat -s pdd) |
| 风险评估 | 低(仅修改一个方法,分支条件清晰,未触碰布局/资源/pipeline 接管逻辑) |
本文来自博客园,作者:阿丟啊,转载请注明原文链接:https://www.cnblogs.com/qiyuexiaxun/p/22555178

浙公网安备 33010602011771号