【SystemUI】锁屏来通知默认亮屏Wake模式
一、问题描述
基于 Android 14平台,锁屏状态下来通知时默认是进入Doze模式,此时屏幕不能点击只能查看通知信息且很快灭屏,用户体验不是很好,要求修改为通知直接亮屏。
二、问题分析
梳理锁屏状态下(特指设备息屏或处于Doze/AOD状态)接收到新通知的完整流程,可以更清晰地理解代码的脉络,这个流程横跨了Android Framework的核心服务和SystemUI。
流程概览:一个App发送通知 -> NotificationManagerService (系统服务) -> SystemUI (UI进程) -> 通知中断判断 -> Doze/亮屏决策 -> 执行动作 (Pulse或WakeUp)
1. 通知的发送与接收
- 应用层 -> 系统服务层
App: 调用 NotificationManager.notify()
方法发送一个通知。
Framework (NotificationManagerService.java): 这是Android系统中负责管理所有通知的核心服务。enqueueNotificationInternal()
接收来自App的通知请求,进行处理、权限检查、分组、排序(Ranking)等一系列操作,然后将通知“排入队列”准备分发。
- 系统服务层 -> SystemUI进程
SystemUI (NotificationListener.java): SystemUI中有一个服务会继承 NotificationListenerService
,用于监听系统中所有的通知事件。当NotificationManagerService
处理完一个新通知后,会回调这个方法 void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap)
,这是通知进入SystemUI的第一个入口。
2. SystemUI的内部处理与中断判断
- 通知信息收集与分发
SystemUI (NotifCollection.java) & (NotificationEntryManager.java): NotificationListener
在收到通知后,会将其传递给这些管理器。它们负责将StatusBarNotification
对象包装成NotificationEntry
对象,这个新对象包含了通知的所有信息和其在UI中的状态。然后,它们会将“有新通知”这个事件分发给各个监听者。
- 决定是否“打扰”用户
SystemUI (NotificationInterruptStateProvider.java): 这个类是决定一个新通知是否应该发出声音、振动或点亮屏幕(Pulse)的核心。 shouldHeadsUp()
它会根据通知的渠道、重要性(Importance)、用户设置(是否开启勿扰模式DND)、设备当前状态等一系列复杂的规则,来判断这个通知是否应该以“抬头通知”(Heads-Up Notification)的形式出现,或者是否应该触发一次Doze Pulse。
3. 从“打扰”到“亮屏”的决策传递
- 触发Doze Host回调
SystemUI (StatusBarNotificationPresenter.java) & (DozeService.java): 当NotificationInterruptStateProvider
判断一个通知应该触发Pulse时,它会通过一系列调用,最终通知到DozeHost
。
接口: DozeHost.Callback
方法: onNotificationAlerted(Runnable onPulseSuppressedListener)
作用: 这是一个回调方法,字面意思就是“被通知提醒了”。它标志着“有一个通知需要唤醒屏幕”这个信号正式传递到了Doze管理体系中。
- DozeTriggers接收信号(您正在修改的地方)
SystemUI (DozeTriggers.java): 这个类实现了DozeHost.Callback
接口,是各种Doze触发器(传感器、通知、充电等)的集中处理地。
成员: mHostCallback
(一个DozeHost.Callback
的实例)
方法: onNotification(Runnable onPulseSuppressedListener)
作用: mHostCallback
在被调用时,会直接执行这个onNotification
方法。这就是整个流程的汇合点,也是您进行修改的最佳位置。之前所有的判断和流程,最终都会走到这里,请求一次屏幕状态的改变。
4. 执行最终动作(Pulse或WakeUp)
在 DozeTriggers.onNotification()
内部 requestPulse(...)
, 这个方法会请求Doze状态机执行一次“脉冲”。它内部会进一步调用proximityCheckThenCall
来检查近距离传感器,如果检查通过,最终会调用mMachine.requestPulse(reason)
,这个方法会请求Doze状态机直接“唤醒”。
SystemUI (DozeMachine.java): 这是一个状态机,负责管理设备在
INITIALIZED
,DOZE
,DOZE_AOD
,DOZE_PULSING
,FINISH
等各种Doze状态之间的转换。
方法:requestPulse(int reason)
效果: 将状态切换到DOZE_PULSING
。这会触发DozeService
在屏幕上绘制AOD通知界面,短暂亮起后熄灭。
方法:wakeUp(int reason)
效果: 将状态切换到FINISH
。这会退出整个Doze流程,并最终通过PowerManager
来完全点亮屏幕**,显示锁屏界面(Keyguard)。
三、解决方案
DozeTriggers.requestPulse()
-> DozeMachine.requestPulse()
-> 屏幕进入Pulse状态
DozeTriggers.mMachine.wakeUp()
-> DozeMachine.wakeUp()
-> 屏幕被完全唤醒
src/com/android/systemui/doze/DozeTriggers.java
private void onNotification(Runnable onPulseSuppressedListener) {// Bug #2190530 [Android14][AR.599.001744.006101.016207][SystemUI] Dimension Log Configuration for SystemUIif (UniSystemuiComponentFactory.getInstance().getDebugConfigs("DozeMachine")) {Log.d(TAG, "requestNotificationPulse");}if (!sWakeDisplaySensorState) {Log.d(TAG, "Wake display false. Pulse denied.");runIfNotNull(onPulseSuppressedListener);mDozeLog.tracePulseDropped("wakeDisplaySensor");return;}mNotificationPulseTime = SystemClock.elapsedRealtime();if (!mConfig.pulseOnNotificationEnabled(mUserTracker.getUserId())) {runIfNotNull(onPulseSuppressedListener);mDozeLog.tracePulseDropped("pulseOnNotificationsDisabled");return;}if (mDozeHost.isAlwaysOnSuppressed()) {runIfNotNull(onPulseSuppressedListener);mDozeLog.tracePulseDropped("dozeSuppressed");return;}- requestPulse(DozeLog.PULSE_REASON_NOTIFICATION, false /* performedProxCheck */,- onPulseSuppressedListener);+ mMachine.wakeUp(DozeLog.PULSE_REASON_NOTIFICATION);mDozeLog.traceNotificationPulse();
}