【SystemUI】锁屏点击通知显示的解锁界面和通知重叠
一、问题描述
基于 Android 14平台,解锁界面的背景被设置为透明时,当锁屏界面点击通知时会跳出解锁界面,此时解锁界面覆盖再通知之上造成重叠,体验不好。我们发现在锁屏界面上,上滑解锁时会折叠通知页面,此时显示的解锁界面会全面显示,没有重叠,需要将锁屏点击通知的交互修改同上。此时在锁屏界面上通知会有点击和长按事件都可以调出解锁界面,需要分 2 部分处理。
二、问题分析
2.1 锁屏点击通知
分析 NotificationClicker.java
代码,发现实际处理通知点击后启动应用(Activity)以及相关解锁逻辑的关键调用是 mNotificationActivityStarter.onNotificationClicked(entry, row);
。 NotificationClicker
本身只负责接收点击事件并进行一些初步的判断(如菜单是否可见、Guts是否暴露等),而核心的启动和解锁流程被封装在了 NotificationActivityStarter
类中。
再分析 NotificationActivityStarter 的实现类 StatusBarNotificationActivityStarter.java
代码后,发现处理点击通知的核心逻辑在 onNotificationClicked
方法中。这个方法区分了两种主要情况:
showOverLockscreen
为true
: 应用可以直接在锁屏界面之上显示。这种情况下,代码已经包含了mShadeController.collapseShade(true);
的调用,所以行为是正确的。showOverLockscreen
为false
: 这是我们需要关注的情况。当设备处于锁屏状态且应用不能直接在锁屏之上显示时,系统需要先弹出解锁界面(Bouncer)。代码通过调用mActivityStarter.dismissKeyguardThenExecute(...)
来处理这个流程。
问题就出在这里:在调用 mActivityStarter.dismissKeyguardThenExecute(...)
之前,代码没有收起通知面板(Shade),而ActivityStarter
会直接在其上显示Bouncer,导致了UI重叠。
正确的修改方案是在 onNotificationClicked
方法中,针对 showOverLockscreen
为 false
的分支,在调用 mActivityStarter.dismissKeyguardThenExecute(...)
之前,手动添加一行代码来收起通知面板。
2.2 锁屏长按通知
长按通知的逻辑路径
- 事件监听: 对通知的长时间按压事件由
ExpandableNotificationRow.java
捕获。 - Guts的开启: 长按操作的目的是为了打开通知的详细设置界面,这个界面在SystemUI中被称为 “Guts”。这个过程由
NotificationGutsManager.java
类来管理。 - 检查锁屏状态:
NotificationGutsManager
在准备显示Guts之前,会通过KeyguardStateController
检查设备是否处于锁定状态。 - 触发解锁: 如果设备是锁定的,
NotificationGutsManager
不能直接显示Guts(因为这可能暴露信息或提供无需验证的操作)。因此,它会调用一个方法来请求系统显示Bouncer,并在用户成功解锁后,再回来执行显示Guts的操作。 - 问题发生: 与之前的单击场景一样,在请求显示Bouncer时,它没有先收起通知面板,导致了Bouncer直接覆盖在当前显示的通知和Guts的开启位置之上。
openGuts
处理从通知菜单行(NotificationMenuRowPlugin.MenuItem
)触发打开Guts的逻辑,这通常是用户在通知上向一侧轻微滑动后点击设置按钮(或其他自定义按钮)时触发的流程,也包括长按。
mActivityStarter.executeRunnableDismissingKeyguard(...)
: 它请求 ActivityStarter
组件去解除锁屏。ActivityStarter
会负责显示解锁界面(Bouncer),并在用户成功解锁后,执行传入的 Runnable r
。代码在调用 executeRunnableDismissingKeyguard
以显示Bouncer之前,没有收起通知面板(Shade),从而导致了UI重叠。
解决方案与之前的场景完全一致:在请求解锁之前,先调用 mShadeController
来收起通知面板。
三、解决方案
1. 锁屏点击通知
src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
/*** Called when a notification is clicked.** @param entry notification that was clicked* @param row row for that notification*/
@Override
public void onNotificationClicked(NotificationEntry entry, ExpandableNotificationRow row) {...if (showOverLockscreen) {mIsCollapsingToShowActivityOverLockscreen = true;postKeyguardAction.onDismiss();} else {//*/ collapse when notification click in keyguard.if (mKeyguardStateController.isShowing()) {mShadeController.collapseShade(false /* animate */);}//*/mActivityStarter.dismissKeyguardThenExecute(postKeyguardAction,null,willLaunchResolverActivity);}
}
2. 锁屏长按通知
src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
/*** Opens guts on the given ExpandableNotificationRow {@code view}. This handles opening guts for* the normal half-swipe and long-press use cases via a circular reveal. When the blocking* helper needs to be shown on the row, this will skip the circular reveal.** @param view ExpandableNotificationRow to open guts on* @param x x coordinate of origin of circular reveal* @param y y coordinate of origin of circular reveal* @param menuItem MenuItem the guts should display* @return true if guts was opened*/
public boolean openGuts(View view,int x,int y,NotificationMenuRowPlugin.MenuItem menuItem) {...//*/ collapse when notification click in keyguard.if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {mShadeController.collapseShade(false /* animate */);}//*/Runnable r = () -> mMainHandler.post(() -> openGutsInternal(view, x, y, menuItem));// If the bouncer shows, it will block the TOUCH_UP event from reaching the notif,// so explicitly mark it as unpressed here to reset the touch animation.view.setPressed(false);mActivityStarter.executeRunnableDismissingKeyguard(r,null /* cancelAction */,false /* dismissShade */,true /* afterKeyguardGone */,true /* deferred */);return true;/*** When {@link CentralSurfaces} doesn't exist, falling through to call* {@link #openGutsInternal(View,int,int,NotificationMenuRowPlugin.MenuItem)}.*/}}return openGutsInternal(view, x, y, menuItem);
}