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 并加入 mStatusBarIconList
    • setIconVisibility(slot, visibility)L384-L392
      public 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() 中先 setIconsetIconVisibility,链路完整。

2.3 图标视图(StatusBarIconView)

  • 文件:StatusBarIconView.java
  • 关键字段:private final boolean mBlocked;final,只在构造器赋值一次
  • set(StatusBarIcon icon) 方法(L418-L420):
    setVisibility(icon.visible && !mBlocked ? VISIBLE : GONE);
    
  • 决定可见性的两个因子:
    1. icon.visible:由 setIconVisibility() 设置
    2. 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() → 返回 mEnabled
    • isBluetoothConnected() → 蓝牙是否已连接设备
    • isBluetoothAudioActive() / isBluetoothAudioProfileOnly() → 音频状态判断
  • 回调注册:PhoneStatusBarPolicyinit()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 import
2. 重写 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.ktScreenRecordChipInteractor.ktPhoneStatusBarPolicy.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 pddadb logcat -s pdd
风险评估 低(仅修改一个方法,分支条件清晰,未触碰布局/资源/pipeline 接管逻辑)
posted @ 2026-08-18 20:13  阿丟啊  阅读(35)  评论(0)    收藏  举报