Android 16 省电模式 Tile 点击导致状态栏收起

省电模式 Tile 点击导致状态栏收起 --- 完整分析文档

1. 问题描述

点击快速设置面板中的省电模式 Tile(BatterySaverTile)后,状态栏/QS 面板被意外收起,不符合产品预期(省电模式只是开关切换,不需要收起面板)。

2. 涉及的关键文件

文件 路径 角色
BatterySaverTile.java core/frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/ 省电模式 Tile 定义
QSTileImpl.java core/frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tileimpl/ Tile 基类,处理点击事件
BatteryControllerImpl.java core/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/ 电量控制器,监听 AOSP 省电广播
BatterySaverUtils.java core/frameworks/base/packages/SystemUI/src/com/android/systemui/power/ 省电模式工具类
PowerManagerService.java core/frameworks/base/services/core/java/com/android/server/power/ 系统层省电模式管理入口
BatterySaverStateMachine.java core/frameworks/base/services/core/java/com/android/server/power/batterysaver/ 分叉点:同时写入 Settings 和调用 Controller
BatterySaverController.java core/frameworks/base/services/core/java/com/android/server/power/batterysaver/ 发送 ACTION_POWER_SAVE_MODE_CHANGED 广播
PowerControllerImpl.java core/vendor/sprd/platform/frameworks/base/services/core/java/com/android/server/power/unisocpower/ Unisoc 省电管理,SettingsObserver 监听 + 发送 ACTION_POWEREX_SAVE_MODE_CHANGED
CentralSurfacesImplExImpl.java core/vendor/sprd/platform/frameworks/base/packages/SystemUI/src_unisoc_systemui/com/unisoc/systemui/statusbar/phone/ Unisoc 扩展,包含 collapsePanels() 调用(根因)
UnisocSuperPowerUtil.java core/vendor/sprd/ex-interface/frameworks/base/packages/SystemUI/src_unisoc_systemui/com/unisoc/systemui/superpower/ Unisoc 超级省电工具类,定义 mode 常量

3. 完整调用链路分析

3.1 总体概览

点击标准省电 Tile 时,实际触发了 两条并行的广播路径

复制代码
PowerManager.setPowerSaveModeEnabled(enable)
        │
        ├──→ [AOSP 路径] → sendBroadcast(ACTION_POWER_SAVE_MODE_CHANGED)
        │                    → BatteryControllerImpl.onReceive() → updatePowerSave()
        │                    → ❌ 此路径无 collapse
        │
        └──→ [Unisoc 路径] → sendBroadcast(ACTION_POWEREX_SAVE_MODE_CHANGED)
                             → CentralSurfacesImplExImpl.handleBroadcastReceiver()
                             → collapsePanels()  ← 根因!

两条路径的桥梁Settings.Global.LOW_POWER_MODE,AOSP 写入,Unisoc 的 ContentObserver 监听,形成隐式耦合。


3.2 完整链路图

复制代码
用户点击 BatterySaverTile
  │
  ▼
QSTileImpl.click() → BatterySaverTile.handleClick()
  │
  ▼
BatteryControllerImpl.setPowerSaveMode()
  │
  ▼
PowerManager.setPowerSaveModeEnabled(enable)          // binder IPC → SystemServer
  │
  ▼
PowerManagerService
  │
  ▼
BatterySaverStateMachine.setBatterySaverEnabledManually(enable)
  │  [line 830]
  ▼
updateStateLocked(true, enabled)  →  STATE_OFF → STATE_MANUAL_ON
  │
  ▼
enableBatterySaverLocked(enable, ...)                 // [line 851] ← 分叉点!

  │
  ├──────────────────────────────────────────────────────────────┐
  │                                                              │
  ▼  [AOSP 路径]                                                 ▼  [Unisoc 路径]
mBatterySaverController                         putGlobalSetting(
.enableBatterySaver(enable)                          Settings.Global.LOW_POWER_MODE,
  │  [line 878]                                       enable ? 1 : 0)
  │                                                   [line 873]
  ▼                                                   │
handleBatterySaverStateChanged(reason)                │  ContentObserver 触发
  │  [line 426]                                        │
  ▼                                                   ▼
sendBroadcastAsUser(                            PowerControllerImpl
  ACTION_POWER_SAVE_MODE_CHANGED)                 SettingsObserver.onChange()
  [line 467]                                        │  [line 2600]
  │                                                  ▼
  ▼                                             updatePowerSaveMode()
BatteryControllerImpl.onReceive()                   │
  │                                                 ▼
  ▼                                             enterPowerSaveMode(newMode)
updatePowerSave()                                   │  [line 4648]
  │                                                 │
  ▼                                                 ▼
firePowerSaveChanged()                          postModeChangeBroadcast()
  │  → BatterySaverTile.onPowerSaveChanged()        │  [line 4715]
  │     (仅 refreshState)                            │
  │  → CentralSurfacesImpl.mCheckBarModes             ▼
  │     (不 collapse)                           sendBroadcast(
  │                                              ACTION_POWEREX_SAVE_MODE_CHANGED)
  │                                                 │
  ▼                                                 ▼
❌ 无 collapse                                CentralSurfacesImplExImpl
                                              .handleBroadcastReceiver()
                                                 │  [line 73]
                                                 │  curMode = MODE_POWERSAVING(2)
                                                 │  preMode = MODE_NONE(5)
                                                 │
                                                 ▼
                                              collapsePanels()  ← 根因!

3.3 第一阶段:Tile 点击 → SystemServer

复制代码
QSTileViewImpl.init()                         # 设置点击监听
  ↓
tile.click(expandable)
  ↓
QSTileImpl.click()                            # 防误触检查 → Handler 发 H.CLICK
  ↓
H.handleMessage(CLICK)                        # handleClick(expandable)
  ↓
BatterySaverTile.handleClick()                # 第134行
  → mBatteryController.setPowerSaveMode(!mPowerSave, expandable)
    ↓
    BatteryControllerImpl.setPowerSaveMode()  # 第216行
      → BatterySaverUtils.setPowerSaveMode()
        → PowerManager.setPowerSaveModeEnabled(enable)   # [跨进程 binder 调用,详见 3.4]

关键点: BatterySaverTile.handleClick() 本身只有一行有效代码------调用 setPowerSaveMode(),没有任何 collapse 相关逻辑。


3.4 跨进程 Binder IPC 调用详解

PowerManager.setPowerSaveModeEnabled(enable) 运行在 SystemUI App 进程PowerManagerService 运行在 SystemServer 进程,两者通过 AIDL + Binder 实现跨进程通信。

3.4.1 客户端(App 进程):PowerManager.java

PowerManager.java 第 2073-2079 行:

java 复制代码
public boolean setPowerSaveModeEnabled(boolean mode) {
    try {
        return mService.setPowerSaveModeEnabled(mode);   // ← Binder IPC 调用
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

其中 mServiceIPowerManager 的 Binder 代理对象(第 1239 行):

java 复制代码
final IPowerManager mService;   // ← Binder proxy,由框架注入

构造函数(第 1260-1266 行)由 Framework 通过 Context.getSystemService() 注入,实际 IPC 代理对象来自 ServiceManager 对 "power" 服务的查找。

3.4.2 AIDL 接口定义

IPowerManager.aidl 第 70 行:

aidl 复制代码
interface IPowerManager {
    // ...
    boolean setPowerSaveModeEnabled(boolean mode);
    // ...
}

AIDL 编译器自动生成:

  • IPowerManager.Stub(服务端抽象类,onTransact() 反序列化并分发)
  • IPowerManager.Proxy(客户端代理,序列化 Parcel 并 transact 走 Binder 驱动)

调用链路:mService.setPowerSaveModeEnabled(mode)Proxy 将 boolean 写入 Parcel → Binder 驱动跨进程传输 → Stub.onTransact() 解包并分发。

3.4.3 服务端(SystemServer 进程):PowerManagerService.java

发布服务 --- PowerManagerService.java 第 1402-1404 行:

java 复制代码
public void onStart() {
    publishBinderService(Context.POWER_SERVICE, mBinderService, ...);
    // ...
}

BinderService 继承 AIDL Stub --- 第 6029 行:

java 复制代码
final class BinderService extends IPowerManager.Stub {

接收 Binder 调用 --- 第 6550-6562 行:

java 复制代码
@Override // Binder call
public boolean setPowerSaveModeEnabled(boolean enabled) {
    if (mContext.checkCallingOrSelfPermission(
            android.Manifest.permission.POWER_SAVER) != PERMISSION_GRANTED) {
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.DEVICE_POWER, null);
    }
    final long ident = Binder.clearCallingIdentity();
    try {
        return setLowPowerModeInternal(enabled);
    } finally {
        Binder.restoreCallingIdentity(ident);
    }
}
  • 权限检查:POWER_SAVERDEVICE_POWER
  • Binder.clearCallingIdentity():临时提升为 system 身份(因为后续写入 Settings 需要系统权限)

内部实现 --- 第 4227-4243 行:

java 复制代码
private boolean setLowPowerModeInternal(boolean enabled) {
    synchronized (mLock) {
        // Unisoc 修改:允许超级省电模式下切换
        if ((mIsPowered && !mInUltraPowerSave) || !mBatterySaverSupported) {
            return false;
        }
        mBatterySaverStateMachine.setBatterySaverEnabledManually(enabled);
        return true;
    }
}
3.4.4 完整 IPC 流程图
复制代码
[App 进程]                                    [SystemServer 进程]
PowerManager.setPowerSaveModeEnabled(mode)
    │
    ▼
mService.setPowerSaveModeEnabled(mode)       ← IPowerManager.Proxy
    │   序列化 boolean → Parcel
    │   Binder.transact()
    │
────│─── Binder 驱动 (内核态) ───────────────────────────
    │
    ▼  Binder 线程池回调
IPowerManager.Stub.onTransact()             ← AIDL 自动生成
    │   反序列化 Parcel → dispatch
    │
    ▼
PowerManagerService.BinderService           ← extends IPowerManager.Stub
    .setPowerSaveModeEnabled(enabled)        # 第 6550 行
    │   权限检查 + clearCallingIdentity
    │
    ▼
PowerManagerService
    .setLowPowerModeInternal(enabled)        # 第 4227 行
    │   守卫条件检查
    │
    ▼
BatterySaverStateMachine
    .setBatterySaverEnabledManually(enabled) # 第 830 行
    │
    ▼
enableBatterySaverLocked(enable, ...)       # 第 851 行 ← 分叉点

3.5 第二阶段:分叉点 --- enableBatterySaverLocked()

BatterySaverStateMachine.java 第 851-897 行:

java 复制代码
private void enableBatterySaverLocked(boolean enable, boolean manual,
        int intReason, String strReason) {
    // ...
    mSettingBatterySaverEnabled = enable;

    // [line 873] ← 桥梁:写入 Settings,触发 Unisoc ContentObserver
    putGlobalSetting(Settings.Global.LOW_POWER_MODE, enable ? 1 : 0);

    // [line 878] ← AOSP 路径:调用 Controller 发广播
    mBatterySaverController.enableBatterySaver(enable, intReason);
    // ...
}

这是整个链路的分叉点,同一个方法中同时做了两件事,两条路径由此并行展开。


3.6 AOSP 路径:ACTION_POWER_SAVE_MODE_CHANGED

BatterySaverController.java 第 289-300 行:

java 复制代码
public void enableBatterySaver(boolean enable, int reason) {
    synchronized (mLock) {
        if (getFullEnabledLocked() == enable) return;
        setFullEnabledLocked(enable);
        if (updatePolicyLevelLocked()) {
            mHandler.postStateChanged(/*sendBroadcast=*/ true, reason);
        }
    }
}

→ Handler 处理 → handleBatterySaverStateChanged(true, reason)

java 复制代码
// [line 467]
Intent intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);

广播被 SystemUI 进程中的 BatteryControllerImpl.onReceive() 接收:

java 复制代码
// [line 325]
} else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
    updatePowerSave();
}

firePowerSaveChanged() → 通知所有 BatteryStateChangeCallback

回调 ① --- BatterySaverTile.onPowerSaveChanged() :仅调用 refreshState(),更新 Tile UI 状态,不 collapse。

回调 ② --- CentralSurfacesImplmBatteryStateChangeCallback

java 复制代码
// CentralSurfacesImpl.java 第 1035 行 --- 注册回调
mBatteryController.observe(mLifecycle, mBatteryStateChangeCallback);

// 第 3207-3216 行 --- 回调定义
private final BatteryController.BatteryStateChangeCallback mBatteryStateChangeCallback =
    new BatteryController.BatteryStateChangeCallback() {
        @Override
        public void onPowerSaveChanged(boolean isPowerSave) {
            mMainExecutor.execute(mCheckBarModes);       // → checkBarModes()
            if (mDozeServiceHost != null) {
                mDozeServiceHost.firePowerSaveChanged(isPowerSave);
            }
        }
    };

// 第 1814 行 --- mCheckBarModes 的定义
mCheckBarModes = this::checkBarModes;   // Runnable 引用

// 第 1771 行 --- checkBarModes() 实现
// 仅检查/更新状态栏和导航栏的 bar transitions(显隐模式),不涉及面板折叠

两个操作分别是:

  • mMainExecutor.execute(mCheckBarModes) → 执行 checkBarModes(),仅更新 bar transitions,不 collapse
  • mDozeServiceHost.firePowerSaveChanged(isPowerSave) → 通知 Doze 相关回调,不 collapse

关键点:AOSP 路径完全不含任何 collapsePanels() 调用。


3.7 Unisoc 路径:ACTION_POWEREX_SAVE_MODE_CHANGED

3.7.1 隐蔽的桥梁:Settings.Global.LOW_POWER_MODE

回到 enableBatterySaverLocked() 第 873 行的 putGlobalSetting(LOW_POWER_MODE, ...),AOSP 写入后,触发 Unisoc PowerControllerImplContentObserver

PowerControllerImpl.java 第 2595-2615 行:

java 复制代码
private final class SettingsObserver extends ContentObserver {
    // 监听 Settings.Global.LOW_POWER_MODE 变化
    // → 触发 updatePowerSaveMode()
}

private void registerForSettings() {
    mSettingsObserver = new SettingsObserver(msgHandler);
    mContext.getContentResolver().registerContentObserver(
        Settings.Global.getUriFor(Settings.Global.LOW_POWER_MODE),
        false, mSettingsObserver, UserHandle.USER_ALL);
}

AOSP 和 Unisoc 之间没有任何直接代码调用关系 ,唯一桥梁就是 Settings.Global.LOW_POWER_MODE 这个系统设置项。

3.7.2 enterPowerSaveMode()所有 mode 都发广播

SettingsObserver 检测到变化后 → updatePowerSaveMode()enterPowerSaveMode(newMode)

java 复制代码
// [line 4648]
private void enterPowerSaveMode(int newMode) {
    int preMode = mPowerSaveMode;
    mPowerSaveMode = newMode;
    notifyPowerModeListeners(newMode);

    // ★ 对所有 mode 都投递广播消息
    Message message = msgHandler.obtainMessage(MSG_SEND_POWER_CHANGED_BROADCAST);
    message.arg1 = newMode;     // MODE_POWERSAVING(2) 或 MODE_ULTRASAVING(4)
    message.arg2 = preMode;
    msgHandler.sendMessageDelayed(message, getDelayTime(preMode));

    switch (newMode) {
        case MODE_ULTRASAVING:
            mPowerManager.setPowerSaveModeEnabled(true);  // 仅超级省电额外操作
            break;
        case MODE_POWERSAVING:
            break;  // 标准省电只是 break,但广播在 switch 之前已投递!
    }
}

关键点: MSG_SEND_POWER_CHANGED_BROADCAST 消息在 switch 之前就已经投递到 Handler,所以 MODE_POWERSAVING 虽是 break,广播照发不误。

3.7.3 postModeChangeBroadcast() 发送广播
java 复制代码
// [line 4715]
private void postModeChangeBroadcast(int newMode, int oldMode) {
    Intent intent = new Intent(PowerManagerEx.ACTION_POWEREX_SAVE_MODE_CHANGED)
            .putExtra(PowerManagerEx.EXTRA_POWEREX_SAVE_MODE, newMode)
            .putExtra(PowerManagerEx.EXTRA_POWEREX_SAVE_PREMODE, oldMode)
            .setFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
                    | Intent.FLAG_RECEIVER_FOREGROUND);
    mContext.sendBroadcast(intent);
}

广播被 SystemUI 进程中的 CentralSurfacesImplExImpl.handleBroadcastReceiver() 接收。


3.8 第三阶段:接收端 --- 根因

CentralSurfacesImplExImpl.java 第 73-112 行:

java 复制代码
public void handleBroadcastReceiver(...) {
    if (action.equals(ACTION_POWEREX_SAVE_MODE_CHANGED)) {
        int curMode = intent.getIntExtra(EXTRA_POWEREX_SAVE_MODE, ...);
        int preMode = intent.getIntExtra(EXTRA_POWEREX_SAVE_PREMODE, ...);

        if (curMode == MODE_ULTRASAVING) {
            enterSuperPowerMode(...);
        } else if (preMode == MODE_ULTRASAVING) {
            exitSuperPowerMode(...);
        }

        // bug 2839283 collapse Panels for superpower status is changed
        // ★ 守卫条件只检查了"面板是否收起",没有区分 mode 类型
        if (centralSurfacesImpl != null && !mShadeSurface.isFullyCollapsed()) {
            mStatusBarManager.collapsePanels();   // ← 根因!
        }
    }
}

collapsePanels() 的守卫条件只检查了"面板是否已收起",没有区分当前是标准省电(MODE_POWERSAVING = 2)还是超级省电(MODE_ULTRASAVING = 4)。而 ACTION_POWEREX_SAVE_MODE_CHANGED 广播对所有 mode 变更 都发送,导致标准省电 Tile 点击后也走到了 collapsePanels()


3.9 Unisoc 省电模式体系说明

常量 含义 触发方式
MODE_INVALID -1 无效 -
MODE_PERFORMANCE 0 性能模式 -
MODE_SMART 1 智能模式 -
MODE_POWERSAVING 2 标准省电模式 BatterySaverTile 点击
MODE_LOWPOWER 3 低电量模式 电量低于阈值自动触发
MODE_ULTRASAVING 4 超级省电模式 超级省电 Tile 点击
MODE_NONE 5 无省电模式 关闭省电时

标准省电 Tile 点击时的 mode 变化:

操作 curMode preMode
开启标准省电 MODE_POWERSAVING (2) MODE_NONE (5)
关闭标准省电 MODE_NONE (5) MODE_POWERSAVING (2)

两个值均不是 MODE_ULTRASAVING (4)。


4. 修复方案

4.1 修改文件

core/vendor/sprd/platform/frameworks/base/packages/SystemUI/src_unisoc_systemui/com/unisoc/systemui/statusbar/phone/CentralSurfacesImplExImpl.java

4.2 修改内容

collapsePanels() 调用前增加 MODE_POWERSAVING 判断,当 curMode 或 preMode 为标准省电(2)时直接 return,避免错误收起面板:

java 复制代码
// 修改前(原代码逻辑):
//bug 2839283 collapse Panels for superpower status is changed
if (centralSurfacesImpl != null && !mShadeSurface.isFullyCollapsed()) {
    if (mStatusBarManager == null)
        mStatusBarManager = (StatusBarManager) mContext.getSystemService(
                android.app.Service.STATUS_BAR_SERVICE);
    mStatusBarManager.collapsePanels();
}

// 修改后(实际代码,第 104-111 行):
//bug 2839283 collapse Panels for superpower status is changed
if (centralSurfacesImpl != null && !mShadeSurface.isFullyCollapsed()) {
    if (mStatusBarManager == null)
        mStatusBarManager = (StatusBarManager) mContext.getSystemService(
                android.app.Service.STATUS_BAR_SERVICE);
    // (Battery Saver)MODE_POWERSAVING skip collapse
    if (curMode == UnisocSuperPowerUtil.MODE_POWERSAVING
            || preMode == UnisocSuperPowerUtil.MODE_POWERSAVING) return;
    mStatusBarManager.collapsePanels();
}

4.3 修复原理

  • 标准省电模式 curModepreModeMODE_POWERSAVING(2)时,提前 returncollapsePanels() 不会执行
  • 超级省电以及其他模式变更不满足条件,正常走到 collapsePanels()

5. 验证方法

handleBroadcastReceiver 中已保留日志打印:

java 复制代码
Log.d(TAG, "action=" + action + ",curMode=" + curMode + ",preMode=" + preMode);

通过 adb logcat -s CentralSurfacesImplExImpl:D 观察:

  • 点击标准省电 Tile:应看到日志但 打印 collapsePanels()
  • 点击超级省电 Tile:应看到日志且打印 collapsePanels() 并收起面板

6. 总结

问题层级 说明
表面现象 点击省电模式 Tile 后状态栏被收起
直接原因 CentralSurfacesImplExImpl.handleBroadcastReceiver() 中的 collapsePanels() 对所有 mode 执行
隐蔽桥梁 Settings.Global.LOW_POWER_MODE --- AOSP BatterySaverStateMachine 写入,Unisoc PowerControllerImplContentObserver 监听,形成隐式耦合
根本原因 ACTION_POWEREX_SAVE_MODE_CHANGED 广播对标准省电和超级省电都发送,但 collapsePanels() 守卫条件没有区分 mode 类型
修复方式 增加 curMode/preMode == MODE_POWERSAVING 的提前 return,标准省电模式不触发收起
相关推荐
杉氧1 小时前
打破边界(二):在 React Native 中嵌入原生 UI 组件 (Native UI Components)
android·前端·react native
古法安卓2 小时前
Android-切换白天黑夜内存不足问题排查
android·java·android studio
聚美智数2 小时前
快递拦截-物流在途拦截-在途拦截API接口介绍
android
flower_drop2 小时前
从 QtScrcpy 到 TabQA:如何在浏览器侧边栏搞定 Android 投屏与测试提单?
android
脚踏实地,坚持不懈!4 小时前
Android 系统工程师(性能/功耗/稳定性)岗位问题深度解析:从内核源码到实战排查(完善版)
android·linux·运维·服务器
Kapaseker4 小时前
适配小米“中”折屏,非得买一台吗
android·kotlin
恋猫de小郭4 小时前
Flutter 状态管理基准测评,一个很有趣的观点
android·前端·flutter
可乐鸡翅yeah_4 小时前
App WebView 加载 M3U8 流媒体踩坑,安卓 iOS 混合开发播放异常定位
android·ios·harmonyos·m3u8·m3u8在线播放
木卫四科技4 小时前
从函数劫持到智能体控制平面:Hook 如何从 Linux-Android 演化到 Agent Runtime
android·linux·人工智能·安全