省电模式 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();
}
}
其中 mService 是 IPowerManager 的 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_SAVER或DEVICE_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。
回调 ② --- CentralSurfacesImpl 的 mBatteryStateChangeCallback:
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,不 collapsemDozeServiceHost.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 PowerControllerImpl 的 ContentObserver:
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 修复原理
- 标准省电模式
curMode或preMode为MODE_POWERSAVING(2)时,提前return→collapsePanels()不会执行 - 超级省电以及其他模式变更不满足条件,正常走到
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 PowerControllerImpl 的 ContentObserver 监听,形成隐式耦合 |
| 根本原因 | ACTION_POWEREX_SAVE_MODE_CHANGED 广播对标准省电和超级省电都发送,但 collapsePanels() 守卫条件没有区分 mode 类型 |
| 修复方式 | 增加 curMode/preMode == MODE_POWERSAVING 的提前 return,标准省电模式不触发收起 |