SystemUI 在子用户下窗口不显示的问题分析

问题现象

  • 在子用户下,SystemUI 通过 WindowManager.addView 创建的窗口不显示
  • 窗口类型为 TYPE_SYSTEM_ALERT 或 TYPE_APPLICATION_OVERLAY

根本原因

  • SystemUI 使用 sharedUserId=”android.uid.systemui”,对应 UID 10140
  • 该 UID 被解析为系统用户(userId=0),导致窗口归属到系统用户
  • 多用户可见性判断依赖 mShowUserId,与当前用户不匹配时被过滤

UID 计算

1
2
3
4
5
6
7
8
// UserHandle.getUserId() 实现
public static @UserIdInt int getUserId(int uid) {
if (MU_ENABLED) {
return uid / PER_USER_RANGE; // 10140 / 100000 = 0
} else {
return UserHandle.USER_SYSTEM; // 0
}
}
  • SystemUI UID: 10140
  • 计算出的用户ID: 0(系统用户)
  • 实际运行用户: 子用户(如用户1)

窗口可见性判断

1
2
3
4
5
// WindowState.showToCurrentUser()
boolean showToCurrentUser() {
final WindowState win = getTopParentWindow();
return win.showForAllUsers() || mWmService.isUserVisible(win.mShowUserId);
}
  • mShowUserId 来自创建窗口时的 userId,对 SystemUI 为 0
  • 在子用户下,isUserVisible(0) 可能为 false,导致窗口被隐藏

窗口类型行为

1
2
3
4
5
6
7
// TYPE_SYSTEM_ALERT 默认行为
/**
* Window type: system window, such as low power alert. These windows
* are always on top of application windows.
* In multiuser systems shows only on the owning user's window.
*/
public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3;
  • TYPE_SYSTEM_ALERT 默认仅对创建用户可见
  • 需要 SYSTEM_FLAG_SHOW_FOR_ALL_USERS 才能跨用户显示
1
2
3
4
5
// TYPE_APPLICATION_OVERLAY 默认行为
/**
* In multi-user systems shows only on the owning user's screen.
*/
public static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;
  • TYPE_APPLICATION_OVERLAY 同样默认仅对创建用户可见

showForAllUsers 逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
boolean showForAllUsers() {
switch (mAttrs.type) {
default:
// 默认只对创建用户可见
if ((mAttrs.privateFlags & SYSTEM_FLAG_SHOW_FOR_ALL_USERS) == 0) {
return false;
}
break;
// 某些系统窗口类型默认对所有用户可见
case TYPE_PHONE:
case TYPE_STATUS_BAR:
// ... 其他系统窗口类型
break;
}
// 需要系统权限
return mOwnerCanAddInternalSystemWindow;
}
  • 未设置 SYSTEM_FLAG_SHOW_FOR_ALL_USERS 时,默认仅对创建用户可见
  • 需要 mOwnerCanAddInternalSystemWindow 权限

解决方案

  • 方案1:设置 SYSTEM_FLAG_SHOW_FOR_ALL_USERS
1
2
3
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
  • 方案2:使用默认跨用户的窗口类型
1
2
3
4
// 使用默认对所有用户可见的窗口类型
params.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
// 或
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
  • 方案3:在 SystemUI 中统一处理
1
2
3
4
// 在 SystemUI 的窗口创建工具类中
public static void setShowForAllUsers(WindowManager.LayoutParams params) {
params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
}

验证

  • 在子用户下创建窗口并检查可见性
  • 确认 mShowUserId 与当前用户匹配
  • 验证 showForAllUsers() 返回 true

总结

  • 根因是 sharedUserId 使 SystemUI 的 UID 被解析为系统用户,窗口归属到系统用户
  • 多用户可见性判断依赖 mShowUserId,与当前用户不匹配时被过滤
  • 通过设置 SYSTEM_FLAG_SHOW_FOR_ALL_USERS 或使用默认跨用户的窗口类型可解决