Android 15 r17 CarUser / UserPickerActivity 启动、切换、退出与新建用户流程整理
本文是作者基于AOSP15_r17的源码进行整理,摘录代码后由AI整理文章格式和相关图示。
已确认代码和图示的正确性,请参考。
版本基线:
android-15.0.0_r17范围:AAOS / CarService / Car SystemUI 中与
CarUserService、UserPickerActivity、User 启动、切换、退出、新建相关的主流程。资料来源:
1.txt中记录的源码路径、调用点和排查结论。
1. 结论摘要
UserPickerActivity的启动入口不在 Launcher 流程中,而是由 Car 侧根据 Display 与 User 绑定关系触发。核心触发点是CarUserService注册的ICarOccupantZoneCallback,当 Occupant Zone 的 Display 或 User 配置变化时调用startUserPicker()。UserPickerActivity已经在某个 Display 上启动后,Framework 的resumeTopActivities()不会再无条件启动 Home/Launcher。RootWindowContainer仅在当前 Display 没有可 resume 的 Activity,且没有 focused root task 时,才通过resumeHomeActivity()拉起 Home。- 首次冷启动时,
CarUserService.priorityInit()会触发 Initial User 决策。若没有有效初始用户,InitialUserSetter会创建一个 Admin 用户并切换;若已有有效初始用户,则切换到该用户。 - 下次开机默认回到上次关联/设置的 boot user,关键在
UserManagerService.setBootUser()与HsumBootUserInitializer.systemRunning()后续switchToBootUser流程。 - 开机后通过状态栏/用户列表切换 Guest、Profile 或普通用户时,入口通常经过 Car SystemUI 的
ProfileSwitcher,再调用CarUserManager/CarUserService.switchUser()。切换过程中的 loading 画面由UserSwitchTransitionViewController/UserSwitchTransitionViewMediator控制。 - Logout 后重新显示
UserPickerActivity的关键链路是:停止当前 user -> user 从 display/visibility/occupant zone 解绑 ->CarOccupantZoneService发出 User 配置变化 ->CarUserService.mOccupantZoneCallback收到变化 ->startUserPicker()。 - 新建用户流程应放在 UserPicker 或用户管理入口中,通过
CarUserManager.createUser()/UserManager创建用户,再执行 display 分配与切换。创建后若当前 Display 无绑定用户,应确保触发 Occupant Zone 同步,否则 UserPicker 可能仍显示或 Launcher 不启动。
2. 涉及模块与关键类
| 模块 | 路径 | 关键职责 |
|---|---|---|
| Car SystemUI UserPicker | packages/apps/Car/SystemUI/src/com/android/systemui/car/userpicker/ |
用户选择页、logout、新建/切换入口 |
| Car SystemUI UserSwitch UI | packages/apps/Car/SystemUI/src/com/android/systemui/car/userswitcher/ |
用户切换 loading / 全屏用户切换 UI |
| CarUserService | packages/services/Car/service/src/com/android/car/user/CarUserService.java |
车载用户生命周期、切换、启动 UserPicker、HAL 协同 |
| InitialUserSetter | packages/services/Car/service/src/com/android/car/user/InitialUserSetter.java |
冷启动初始用户决策、创建/切换 boot user |
| CarActivityService | packages/services/Car/service/src/com/android/car/am/CarActivityService.java |
按 Display 启动 UserPicker |
| CarOccupantZoneService | packages/services/Car/service/src/com/android/car/CarOccupantZoneService.java |
Occupant Zone、Display/User 绑定与变更通知 |
| CarServiceHelperService | frameworks/opt/car/services/builtInServices/src/com/android/internal/car/CarServiceHelperService.java |
system_server 与 CarService 之间的生命周期桥接 |
| HsumBootUserInitializer | frameworks/base/services/java/com/android/server/HsumBootUserInitializer.java |
HSUM 模式下解锁 system user、切换 boot user |
| UserController | frameworks/base/services/core/java/com/android/server/am/UserController.java |
Framework 层 stop/switch user 状态机 |
| UserVisibilityMediator | frameworks/base/services/core/java/com/android/server/pm/UserVisibilityMediator.java |
Visible user 变化通知 |
| RootWindowContainer | frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java |
Activity resume / Home 启动兜底 |
3. UserPickerActivity 启动流程
3.1 UserPicker 组件配置
UserPickerActivity 组件名由 CarService 的资源配置读取:
xml
<!-- packages/services/Car/service/res/values/config.xml -->
<string name="config_userPickerActivity">
com.android.systemui/.car.userpicker.UserPickerActivity
</string>
CarActivityService.startUserPickerOnDisplay() 从 config_userPickerActivity 读取组件名,并在指定 Display 上启动 UserPicker。
java
public void startUserPickerOnDisplay(int displayId) {
try {
Trace.beginSection("CarActivityService-startUserPickerOnDisplay: " + displayId);
CarServiceUtils.assertAnyPermission(mContext, INTERACT_ACROSS_USERS);
Preconditions.checkArgument(displayId != Display.INVALID_DISPLAY, "Invalid display");
String userPickerName = mContext.getResources()
.getString(R.string.config_userPickerActivity);
if (userPickerName.isEmpty()) {
Slogf.w(TAG, "Cannot launch user picker to display %d, component not specified",
displayId);
return;
}
CarServiceUtils.startUserPickerOnDisplay(mContext, displayId, userPickerName);
} finally {
Trace.endSection();
}
}
3.2 Occupant Zone 变化触发 UserPicker
CarUserService.init() 在支持多用户多 Display 时,向 CarOccupantZoneService 注册回调:
java
@Override
public void init() {
if (isMultipleUsersOnMultipleDisplaysSupported(mUserManager)) {
mCarOccupantZoneService.registerCallback(mOccupantZoneCallback);
}
}
当 Display 或 User 与 Occupant Zone 的关系变化时,CarUserService 收到 ZONE_CONFIG_CHANGE_FLAG_DISPLAY 或 ZONE_CONFIG_CHANGE_FLAG_USER,随后调用 startUserPicker():
java
private final ICarOccupantZoneCallback mOccupantZoneCallback =
new ICarOccupantZoneCallback.Stub() {
@Override
public void onOccupantZoneConfigChanged(int flags) throws RemoteException {
if ((flags & (CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY
| CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER)) != 0) {
startUserPicker();
}
}
};
3.3 启动链路图
UserPickerActivity CarServiceUtils CarActivityService CarUserService CarOccupantZoneService UserPickerActivity CarServiceUtils CarActivityService CarUserService CarOccupantZoneService #mermaid-svg-23SBtTflf1eL9ea2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-23SBtTflf1eL9ea2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-23SBtTflf1eL9ea2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-23SBtTflf1eL9ea2 .error-icon{fill:#552222;}#mermaid-svg-23SBtTflf1eL9ea2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-23SBtTflf1eL9ea2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-23SBtTflf1eL9ea2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-23SBtTflf1eL9ea2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-23SBtTflf1eL9ea2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-23SBtTflf1eL9ea2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-23SBtTflf1eL9ea2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-23SBtTflf1eL9ea2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-23SBtTflf1eL9ea2 .marker.cross{stroke:#333333;}#mermaid-svg-23SBtTflf1eL9ea2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-23SBtTflf1eL9ea2 p{margin:0;}#mermaid-svg-23SBtTflf1eL9ea2 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-23SBtTflf1eL9ea2 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-23SBtTflf1eL9ea2 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-23SBtTflf1eL9ea2 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-23SBtTflf1eL9ea2 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-23SBtTflf1eL9ea2 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-23SBtTflf1eL9ea2 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-23SBtTflf1eL9ea2 .sequenceNumber{fill:white;}#mermaid-svg-23SBtTflf1eL9ea2 #sequencenumber{fill:#333;}#mermaid-svg-23SBtTflf1eL9ea2 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-23SBtTflf1eL9ea2 .messageText{fill:#333;stroke:none;}#mermaid-svg-23SBtTflf1eL9ea2 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-23SBtTflf1eL9ea2 .labelText,#mermaid-svg-23SBtTflf1eL9ea2 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-23SBtTflf1eL9ea2 .loopText,#mermaid-svg-23SBtTflf1eL9ea2 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-23SBtTflf1eL9ea2 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-23SBtTflf1eL9ea2 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-23SBtTflf1eL9ea2 .noteText,#mermaid-svg-23SBtTflf1eL9ea2 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-23SBtTflf1eL9ea2 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-23SBtTflf1eL9ea2 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-23SBtTflf1eL9ea2 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-23SBtTflf1eL9ea2 .actorPopupMenu{position:absolute;}#mermaid-svg-23SBtTflf1eL9ea2 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-23SBtTflf1eL9ea2 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-23SBtTflf1eL9ea2 .actor-man circle,#mermaid-svg-23SBtTflf1eL9ea2 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-23SBtTflf1eL9ea2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} onOccupantZoneConfigChanged(DISPLAY/USER) 1 startUserPicker() 2 startUserPickerOnDisplay(displayId) 3 read config_userPickerActivity 4 startUserPickerOnDisplay(context, displayId, component) 5 startActivityAsUser + launch display options 6
4. 为什么 resume 流程没有启动 Launcher
系统启动或 Activity 栈恢复时,AMS 会触发 ActivityTaskManager resume 顶层 Activity:
java
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
t.traceBegin("resumeTopActivities");
mAtmInternal.resumeTopActivities(false /* scheduleIdle */);
t.traceEnd();
后续进入 RootWindowContainer.resumeFocusedTasksTopActivities()。关键判断是:如果某个 Display 上已经 resume 了 Activity,例如 UserPickerActivity,或者存在 focused root task,则不会进入 Home 兜底启动。
java
boolean resumeFocusedTasksTopActivities() {
return resumeFocusedTasksTopActivities(null, null, null,
false /* deferPause */);
}
java
if (!resumedOnDisplay[0]) {
final Task focusedRoot = display.getFocusedRootTask();
if (focusedRoot != null) {
result |= focusedRoot.resumeTopActivityUncheckedLocked(
target, targetOptions, false /* skipPause */);
} else if (targetRootTask == null) {
result |= resumeHomeActivity(null /* prev */, "no-focusable-task",
display.getDefaultTaskDisplayArea());
}
}
因此,当前 Display 已经启动 UserPickerActivity 时,Launcher 并不会再被重复拉起;只有没有可恢复 Activity、没有 focused root task 且 targetRootTask 为 null 时,才会进入 resumeHomeActivity()。
4.1 判断图
#mermaid-svg-XrkLVwuFFBQ4i8x8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XrkLVwuFFBQ4i8x8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .error-icon{fill:#552222;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .marker.cross{stroke:#333333;}#mermaid-svg-XrkLVwuFFBQ4i8x8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XrkLVwuFFBQ4i8x8 p{margin:0;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .cluster-label text{fill:#333;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .cluster-label span{color:#333;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .cluster-label span p{background-color:transparent;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .label text,#mermaid-svg-XrkLVwuFFBQ4i8x8 span{fill:#333;color:#333;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .node rect,#mermaid-svg-XrkLVwuFFBQ4i8x8 .node circle,#mermaid-svg-XrkLVwuFFBQ4i8x8 .node ellipse,#mermaid-svg-XrkLVwuFFBQ4i8x8 .node polygon,#mermaid-svg-XrkLVwuFFBQ4i8x8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .rough-node .label text,#mermaid-svg-XrkLVwuFFBQ4i8x8 .node .label text,#mermaid-svg-XrkLVwuFFBQ4i8x8 .image-shape .label,#mermaid-svg-XrkLVwuFFBQ4i8x8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .rough-node .label,#mermaid-svg-XrkLVwuFFBQ4i8x8 .node .label,#mermaid-svg-XrkLVwuFFBQ4i8x8 .image-shape .label,#mermaid-svg-XrkLVwuFFBQ4i8x8 .icon-shape .label{text-align:center;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .node.clickable{cursor:pointer;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .arrowheadPath{fill:#333333;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XrkLVwuFFBQ4i8x8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XrkLVwuFFBQ4i8x8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XrkLVwuFFBQ4i8x8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .cluster text{fill:#333;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .cluster span{color:#333;}#mermaid-svg-XrkLVwuFFBQ4i8x8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XrkLVwuFFBQ4i8x8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .icon-shape,#mermaid-svg-XrkLVwuFFBQ4i8x8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .icon-shape p,#mermaid-svg-XrkLVwuFFBQ4i8x8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .icon-shape .label rect,#mermaid-svg-XrkLVwuFFBQ4i8x8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XrkLVwuFFBQ4i8x8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XrkLVwuFFBQ4i8x8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XrkLVwuFFBQ4i8x8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
是
否
是
否
AMS resumeTopActivities
RootWindowContainer.resumeFocusedTasksTopActivities
当前 Display 是否已有 Activity resumed?
不启动 Home
是否存在 focusedRoot?
resume focusedRoot top Activity
targetRootTask == null?
resumeHomeActivity 启动 Launcher/Home
不启动 Home
5. 首次开机:创建并切换 Driver / Admin 用户
5.1 CarService 初始化入口
CarService 初始化时,ICarImpl.init() 会优先初始化用户:
java
@MainThread
void init() {
TimingsTraceLog t = new TimingsTraceLog(
CAR_SERVICE_INIT_TIMING_TAG,
TraceHelper.TRACE_TAG_CAR_SERVICE,
CAR_SERVICE_INIT_TIMING_MIN_DURATION_MS);
// 初始化各 CarService,其中 CarUserService 会执行 priorityInit()
}
CarUserService.priorityInit() 将 boot user 选择逻辑投递到 handler:
java
public void priorityInit() {
mHandler.post(() -> initBootUser(getInitialUserInfoRequestType()));
}
冷启动时,getInitialUserInfoRequestType() 对应 InitialUserInfoRequestType.COLD_BOOT。
5.2 InitialUserSetter 默认行为
如果 User HAL 不提供特定用户,或需要 fallback,CarUserService 会构造默认行为请求:
java
private void fallbackToDefaultInitialUserBehavior(String userLocales, boolean replaceGuest,
boolean supportsOverrideUserIdProperty, int requestType) {
InitialUserInfo info = new InitialUserSetter.Builder(
InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
.setRequestType(requestType)
.setUserLocales(userLocales)
.setReplaceGuest(replaceGuest)
.setSupportsOverrideUserIdProperty(supportsOverrideUserIdProperty)
.build();
mInitialUserSetter.set(info);
}
InitialUserSetter.executeDefaultBehavior() 的关键分支:
java
private void executeDefaultBehavior(@NonNull InitialUserInfo info, boolean fallback) {
if (!hasValidInitialUser()) {
createAndSwitchUser(new Builder(TYPE_CREATE)
.setNewUserName(mNewUserName)
.setNewUserFlags(UserInfo.USER_FLAG_ADMIN)
.setSupportsOverrideUserIdProperty(info.supportsOverrideUserIdProperty)
.setUserLocales(info.userLocales)
.build(), fallback);
} else {
int userId = getInitialUser(info.supportsOverrideUserIdProperty);
switchUser(new Builder(TYPE_SWITCH)
.setSwitchUserId(userId)
.setSupportsOverrideUserIdProperty(info.supportsOverrideUserIdProperty)
.setReplaceGuest(info.replaceGuest)
.build(), fallback);
}
}
5.3 创建用户并切换
没有有效初始用户时进入创建流程:
java
private void createAndSwitchUser(@NonNull InitialUserInfo info, boolean fallback) {
Pair<UserHandle, String> result = createNewUser(info);
String reason = result.second;
if (reason != null) {
fallbackDefaultBehavior(info, fallback, reason);
return;
}
// 创建成功后继续 switchUser / setBootUser 等后续动作
}
新用户类型根据 HAL flags 判断 Guest 或 Secondary:
java
int flags = UserHalHelper.toUserInfoFlags(halFlags);
String type = UserHalHelper.isGuest(halFlags)
? UserManager.USER_TYPE_FULL_GUEST
: UserManager.USER_TYPE_FULL_SECONDARY;
在完成 boot user 选择后,UserManagerService.setBootUser() 记录 boot user,并释放等待锁:
java
@Override
public void setBootUser(@UserIdInt int userId) {
checkCreateUsersPermission("Set boot user");
synchronized (mUsersLock) {
Slogf.i(LOG_TAG, "setBootUser %d", userId);
mBootUser = userId;
}
mBootUserLatch.countDown();
}
5.4 Framework 侧切换 boot user
HsumBootUserInitializer.systemRunning() 会先观测设备 provision 状态,再解锁 system user,随后进入 boot user 切换:
java
public void systemRunning(TimingsTraceAndSlog t) {
observeDeviceProvisioning();
unlockSystemUser(t);
// 后续等待并切换到 boot user
}
5.5 首次开机流程图
AMS/UserController HsumBootUserInitializer UserManagerService InitialUserSetter CarUserService CarService / ICarImpl AMS/UserController HsumBootUserInitializer UserManagerService InitialUserSetter CarUserService CarService / ICarImpl #mermaid-svg-DoKokbI2zibrQp7x{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-DoKokbI2zibrQp7x .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-DoKokbI2zibrQp7x .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-DoKokbI2zibrQp7x .error-icon{fill:#552222;}#mermaid-svg-DoKokbI2zibrQp7x .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-DoKokbI2zibrQp7x .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-DoKokbI2zibrQp7x .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-DoKokbI2zibrQp7x .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-DoKokbI2zibrQp7x .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-DoKokbI2zibrQp7x .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-DoKokbI2zibrQp7x .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-DoKokbI2zibrQp7x .marker{fill:#333333;stroke:#333333;}#mermaid-svg-DoKokbI2zibrQp7x .marker.cross{stroke:#333333;}#mermaid-svg-DoKokbI2zibrQp7x svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-DoKokbI2zibrQp7x p{margin:0;}#mermaid-svg-DoKokbI2zibrQp7x .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-DoKokbI2zibrQp7x text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-DoKokbI2zibrQp7x .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-DoKokbI2zibrQp7x .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-DoKokbI2zibrQp7x .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-DoKokbI2zibrQp7x .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-DoKokbI2zibrQp7x #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-DoKokbI2zibrQp7x .sequenceNumber{fill:white;}#mermaid-svg-DoKokbI2zibrQp7x #sequencenumber{fill:#333;}#mermaid-svg-DoKokbI2zibrQp7x #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-DoKokbI2zibrQp7x .messageText{fill:#333;stroke:none;}#mermaid-svg-DoKokbI2zibrQp7x .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-DoKokbI2zibrQp7x .labelText,#mermaid-svg-DoKokbI2zibrQp7x .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-DoKokbI2zibrQp7x .loopText,#mermaid-svg-DoKokbI2zibrQp7x .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-DoKokbI2zibrQp7x .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-DoKokbI2zibrQp7x .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-DoKokbI2zibrQp7x .noteText,#mermaid-svg-DoKokbI2zibrQp7x .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-DoKokbI2zibrQp7x .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-DoKokbI2zibrQp7x .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-DoKokbI2zibrQp7x .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-DoKokbI2zibrQp7x .actorPopupMenu{position:absolute;}#mermaid-svg-DoKokbI2zibrQp7x .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-DoKokbI2zibrQp7x .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-DoKokbI2zibrQp7x .actor-man circle,#mermaid-svg-DoKokbI2zibrQp7x line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-DoKokbI2zibrQp7x :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} alt 无有效初始用户 有有效初始用户 priorityInit() 1 initBootUser(COLD_BOOT) 2 set(TYPE_DEFAULT_BEHAVIOR) 3 hasValidInitialUser? 4 createNewUser(Admin/Secondary) 5 switchUser(newUser) 6 switchUser(initialUser) 7 setBootUser(userId) 8 unlockSystemUser() 9 switchToBootUser(userId) 10 start/resume Home or target activity 11
6. 下次开机默认启动上次切换用户
下次开机时,CarUserService.priorityInit() 仍会走 initBootUser()。差异在于已有有效初始用户,InitialUserSetter 会进入 TYPE_SWITCH 或默认行为中的 switch 分支。
java
public void set(@NonNull InitialUserInfo info) {
switch (info.type) {
case TYPE_SWITCH:
try {
switchUser(info, true /* fallback */);
} catch (Exception e) {
fallbackDefaultBehavior(info, true /* fallback */,
"Exception switching user: " + e);
}
break;
default:
// 其他 TYPE_CREATE / TYPE_DEFAULT_BEHAVIOR 分支
break;
}
}
实际是否回到上次用户,取决于以下因素:
UserManagerService中 boot user 是否已设置为上次目标用户;- User HAL 是否返回覆盖的 initial user 信息;
- 是否支持
supportsOverrideUserIdProperty; - Guest 是否需要 replace;
- HSUM/可见后台用户配置是否启用。
7. 开机后用户切换与 loading 画面
7.1 状态栏 ProfileSwitcher 入口
Car SystemUI 中用户列表点击后会进入 ProfileSwitcher.switchUser(userInfo.id):
java
protected QCRow createUserProfileRow(UserInfo userInfo) {
QCItem.ActionHandler actionHandler = (item, context, intent) -> {
if (mPendingUserAdd) {
return;
}
switchUser(userInfo.id);
};
boolean isCurrentProfile = userInfo.id == mUserTracker.getUserId();
// 构造 row UI
}
7.2 CarUserService.switchUser()
CarUserService.switchUser() 先校验目标用户存在,再投递到 handler 处理:
java
@Override
public void switchUser(@UserIdInt int targetUserId, int timeoutMs,
@NonNull ResultCallbackImpl callback, boolean ignoreUxRestriction) {
UserHandle targetUser = mUserHandleHelper.getExistingUserHandle(targetUserId);
mHandler.post(() -> handleSwitchUser(targetUser, timeoutMs, callback,
false /* isLogout */, ignoreUxRestriction));
}
User HAL 不支持时,直接走 Android Framework 用户切换:
java
if (!isUserHalSupported()) {
int result = switchOrLogoutUser(targetUser, isLogout);
if (result == UserManager.USER_OPERATION_SUCCESS) {
sendUserSwitchResult(callback, isLogout,
UserSwitchResult.STATUS_SUCCESSFUL);
return;
}
sendUserSwitchResult(callback, isLogout, HalCallback.STATUS_INVALID,
UserSwitchResult.STATUS_ANDROID_FAILURE, result,
null /* errorMessage */);
return;
}
7.3 loading 画面来源
UserSwitchTransitionViewController 负责切换时显示过渡层,并通知 WMS 当前处于 switching user 状态:
java
void handleShow(@UserIdInt int newUserId) {
mMainExecutor.execute(() -> {
if (mPreviousUserId == newUserId || mTransitionViewShowing) {
return;
}
mTransitionViewShowing = true;
try {
mWindowManagerService.setSwitchingUser(true);
if (!refactorGetCurrentUser()) {
mWindowManagerService.lockNow(null);
}
} catch (RemoteException e) {
Log.e(TAG, "unable to notify window manager service regarding user switch");
}
});
}
相关 mediator 由 Car SystemUI 资源配置反射初始化:
xml
<!-- packages/apps/Car/SystemUI/res/values/config.xml -->
<string-array name="config_systemUIFactoryComponents">
<item>com.android.systemui.car.hvac.HvacPanelOverlayViewMediator</item>
<item>com.android.systemui.car.keyguard.CarKeyguardOverlayViewMediator</item>
<item>com.android.systemui.car.systemdialogs.SystemDialogsViewMediator</item>
<item>com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator</item>
<item>com.android.systemui.car.userswitcher.UserSwitchTransitionViewMediator</item>
</string-array>
7.4 切换流程图
UserSwitchTransitionViewController AMS/UserController User HAL CarUserService CarUserManager ProfileSwitcher/UserPicker UI UserSwitchTransitionViewController AMS/UserController User HAL CarUserService CarUserManager ProfileSwitcher/UserPicker UI #mermaid-svg-rdezpgzthYq0ZrOv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rdezpgzthYq0ZrOv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rdezpgzthYq0ZrOv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rdezpgzthYq0ZrOv .error-icon{fill:#552222;}#mermaid-svg-rdezpgzthYq0ZrOv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rdezpgzthYq0ZrOv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rdezpgzthYq0ZrOv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rdezpgzthYq0ZrOv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rdezpgzthYq0ZrOv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rdezpgzthYq0ZrOv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rdezpgzthYq0ZrOv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rdezpgzthYq0ZrOv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rdezpgzthYq0ZrOv .marker.cross{stroke:#333333;}#mermaid-svg-rdezpgzthYq0ZrOv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rdezpgzthYq0ZrOv p{margin:0;}#mermaid-svg-rdezpgzthYq0ZrOv .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-rdezpgzthYq0ZrOv text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-rdezpgzthYq0ZrOv .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-rdezpgzthYq0ZrOv .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-rdezpgzthYq0ZrOv .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-rdezpgzthYq0ZrOv .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-rdezpgzthYq0ZrOv #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-rdezpgzthYq0ZrOv .sequenceNumber{fill:white;}#mermaid-svg-rdezpgzthYq0ZrOv #sequencenumber{fill:#333;}#mermaid-svg-rdezpgzthYq0ZrOv #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-rdezpgzthYq0ZrOv .messageText{fill:#333;stroke:none;}#mermaid-svg-rdezpgzthYq0ZrOv .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-rdezpgzthYq0ZrOv .labelText,#mermaid-svg-rdezpgzthYq0ZrOv .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-rdezpgzthYq0ZrOv .loopText,#mermaid-svg-rdezpgzthYq0ZrOv .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-rdezpgzthYq0ZrOv .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-rdezpgzthYq0ZrOv .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-rdezpgzthYq0ZrOv .noteText,#mermaid-svg-rdezpgzthYq0ZrOv .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-rdezpgzthYq0ZrOv .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-rdezpgzthYq0ZrOv .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-rdezpgzthYq0ZrOv .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-rdezpgzthYq0ZrOv .actorPopupMenu{position:absolute;}#mermaid-svg-rdezpgzthYq0ZrOv .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-rdezpgzthYq0ZrOv .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-rdezpgzthYq0ZrOv .actor-man circle,#mermaid-svg-rdezpgzthYq0ZrOv line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-rdezpgzthYq0ZrOv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} alt User HAL supported User HAL not supported switchUser(targetUserId) 1 binder switchUser() 2 handleSwitchUser() 3 handleShow(newUserId) 4 switch user request 5 result 6 switchOrLogoutUser(targetUser) 7 USER_OPERATION_SUCCESS / failure 8 UserSwitchResult 9 hide transition view after switch completed 10
8. Logout 后显示 UserPickerActivity
8.1 UserPickerController logout 入口
用户在 UserPicker UI 或相关入口选择 logout 后,UserPickerController.logoutUser() 会弹出确认框:
java
void logoutUser() {
mIsUserPickerClickable = false;
int userId = mCarServiceMediator.getUserForDisplay(mDisplayId);
if (userId != INVALID_USER_ID) {
mDialogManager.showDialog(
DIALOG_TYPE_CONFIRM_LOGOUT,
() -> logoutUserInternal(userId),
() -> mIsUserPickerClickable = true);
}
}
UserEventManager 调用 CarUserManager.stopUser() 停止当前 Display 关联用户:
java
carUserManager.stopUser(
new UserStopRequest.Builder(UserHandle.of(userId)).build(),
Runnable::run,
userStopCallback);
8.2 CarUserService.stopUser()
java
@Override
public void stopUser(UserStopRequest request, ResultCallbackImpl callback) {
if (!hasManageUsersOrPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)) {
throw new SecurityException("stopUser: You need one of "
+ MANAGE_USERS + ", or " + INTERACT_ACROSS_USERS);
}
int userId = request.getUserHandle().getIdentifier();
boolean withDelayedLocking = request.isWithDelayedLocking();
boolean forceStop = request.isForce();
EventLogHelper.writeCarUserServiceStopUserReq(userId);
mHandler.post(() -> handleStopUser(userId, forceStop,
withDelayedLocking, callback));
}
8.3 Framework stop user 与 display 解绑
ActivityManagerService.stopUserWithDelayedLocking() 进入 UserController.stopUser():
java
@Override
public int stopUserWithDelayedLocking(@UserIdInt int userId,
IStopUserCallback callback) {
return mUserController.stopUser(userId,
true /* allowDelayedLocking */,
callback,
null /* keyEvictedCallback */);
}
UserController.stopUsersLU() 会遍历需要停止的 user,并调用 stopSingleUserLU():
java
private int stopUsersLU(final int userId,
boolean stopProfileRegardlessOfParent,
boolean allowDelayedLocking,
final IStopUserCallback stopUserCallback,
KeyEvictedCallback keyEvictedCallback) {
final int[] usersToStop = getUsersToStopLU(userId);
for (int userIdToStop : usersToStop) {
stopSingleUserLU(userIdToStop, allowDelayedLocking,
userIdToStop == userId ? stopUserCallback : null,
userIdToStop == userId ? keyEvictedCallback : null);
}
return USER_OP_SUCCESS;
}
停止时会设置 user state,并调用 unassignUserFromDisplayOnStop():
java
if (uss.state != UserState.STATE_STOPPING
&& uss.state != UserState.STATE_SHUTDOWN) {
uss.setState(UserState.STATE_STOPPING);
UserManagerInternal userManagerInternal = mInjector.getUserManagerInternal();
userManagerInternal.setUserState(userId, uss.state);
userManagerInternal.unassignUserFromDisplayOnStop(userId);
}
8.4 User visibility 变化传回 CarService
UserManagerService.unassignUserFromDisplayOnStop() 调用 UserVisibilityMediator:
java
@Override
public void unassignUserFromDisplayOnStop(@UserIdInt int userId) {
mUserVisibilityMediator.unassignUserFromDisplayOnStop(userId);
}
UserVisibilityMediator 对比前后可见 user,若某 user 从 visible 变为 invisible,则分发回调:
java
private void dispatchVisibilityChanged(IntArray visibleUsersBefore,
IntArray visibleUsersAfter) {
for (int i = 0; i < visibleUsersBefore.size(); i++) {
int userId = visibleUsersBefore.get(i);
if (visibleUsersAfter.indexOf(userId) == -1) {
dispatchVisibilityChanged(mListeners, userId, false /* visible */);
}
}
for (int i = 0; i < visibleUsersAfter.size(); i++) {
int userId = visibleUsersAfter.get(i);
if (visibleUsersBefore.indexOf(userId) == -1) {
dispatchVisibilityChanged(mListeners, userId, true /* visible */);
}
}
}
CarServiceHelperService 注册了 visibility listener,收到变化后同步给 CarService,并更新 launch params:
java
umi.addUserVisibilityListener(new UserVisibilityListener() {
@Override
public void onUserVisibilityChanged(@UserIdInt int userId, boolean visible) {
int eventType = visible
? USER_LIFECYCLE_EVENT_TYPE_VISIBLE
: USER_LIFECYCLE_EVENT_TYPE_INVISIBLE;
mCarServiceHelperServiceUpdatable.sendUserLifecycleEvent(
eventType, null /* userFrom */, UserHandle.of(userId));
mCarLaunchParamsModifier.handleUserVisibilityChanged(userId, visible);
}
});
最终 Binder 到 Car 进程,进入 CarUserService.onUserLifecycleEvent(),并可能引起 CarOccupantZoneService 解绑用户、同步 config 变化。
java
public void onUserLifecycleEvent(@UserLifecycleEventType int eventType,
@UserIdInt int fromUserId, @UserIdInt int toUserId) {
int userId = toUserId;
// 分发给 CarUserService 内部和注册 listener
}
CarOccupantZoneService 解绑用户后发出 User 配置变化:
java
private int unassignOccupantZoneUnchecked(int occupantZoneId) {
// 更新 occupant zone 与 user/display 绑定关系
syncStateAndSendConfigChangeEvent(
CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER);
return occupantZoneId;
}
该事件再次回到 CarUserService.mOccupantZoneCallback,触发 startUserPicker()。
8.5 Logout 流程图
UserPickerActivity CarOccupantZoneService CarServiceHelperService UserVisibilityMediator ActivityManagerService/UserController CarUserService UserEventManager UserPickerController UserPickerActivity CarOccupantZoneService CarServiceHelperService UserVisibilityMediator ActivityManagerService/UserController CarUserService UserEventManager UserPickerController #mermaid-svg-3gzLu9lUAKWILNR7{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3gzLu9lUAKWILNR7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3gzLu9lUAKWILNR7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3gzLu9lUAKWILNR7 .error-icon{fill:#552222;}#mermaid-svg-3gzLu9lUAKWILNR7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3gzLu9lUAKWILNR7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3gzLu9lUAKWILNR7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3gzLu9lUAKWILNR7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3gzLu9lUAKWILNR7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3gzLu9lUAKWILNR7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3gzLu9lUAKWILNR7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3gzLu9lUAKWILNR7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3gzLu9lUAKWILNR7 .marker.cross{stroke:#333333;}#mermaid-svg-3gzLu9lUAKWILNR7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3gzLu9lUAKWILNR7 p{margin:0;}#mermaid-svg-3gzLu9lUAKWILNR7 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-3gzLu9lUAKWILNR7 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-3gzLu9lUAKWILNR7 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-3gzLu9lUAKWILNR7 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-3gzLu9lUAKWILNR7 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-3gzLu9lUAKWILNR7 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-3gzLu9lUAKWILNR7 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-3gzLu9lUAKWILNR7 .sequenceNumber{fill:white;}#mermaid-svg-3gzLu9lUAKWILNR7 #sequencenumber{fill:#333;}#mermaid-svg-3gzLu9lUAKWILNR7 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-3gzLu9lUAKWILNR7 .messageText{fill:#333;stroke:none;}#mermaid-svg-3gzLu9lUAKWILNR7 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-3gzLu9lUAKWILNR7 .labelText,#mermaid-svg-3gzLu9lUAKWILNR7 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-3gzLu9lUAKWILNR7 .loopText,#mermaid-svg-3gzLu9lUAKWILNR7 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-3gzLu9lUAKWILNR7 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-3gzLu9lUAKWILNR7 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-3gzLu9lUAKWILNR7 .noteText,#mermaid-svg-3gzLu9lUAKWILNR7 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-3gzLu9lUAKWILNR7 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-3gzLu9lUAKWILNR7 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-3gzLu9lUAKWILNR7 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-3gzLu9lUAKWILNR7 .actorPopupMenu{position:absolute;}#mermaid-svg-3gzLu9lUAKWILNR7 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-3gzLu9lUAKWILNR7 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-3gzLu9lUAKWILNR7 .actor-man circle,#mermaid-svg-3gzLu9lUAKWILNR7 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-3gzLu9lUAKWILNR7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} show confirm logout dialog 1 logoutUserInternal(userId) 2 carUserManager.stopUser(UserStopRequest) 3 stopUser / stopUserWithDelayedLocking 4 unassignUserFromDisplayOnStop(userId) 5 onUserVisibilityChanged(userId, false) 6 sendUserLifecycleEvent(INVISIBLE) 7 update/unassign occupant zone user 8 onOccupantZoneConfigChanged(USER) 9 startUserPicker() 10
9. 新建 User 流程补充
原始排查记录中没有完整展开新建用户流程。基于 Android 15 r17 的 CarUser 设计,新建用户建议遵循以下路径:
- UI 层从
UserPickerActivity、UserPickerController或独立用户管理入口发起新建; - 使用
CarUserManager.createUser()或 FrameworkUserManager.createUser()创建普通用户、Guest 或 Admin; - 创建成功后,按业务需要执行:
- 分配 user 到当前 Display / Occupant Zone;
- 切换到新 user;
- 设置 boot user;
- 刷新 UserPicker 列表或关闭 UserPicker;
- 如果创建失败,应返回明确失败码,并恢复 UI 可点击状态。
9.1 推荐 UI 控制伪代码
java
private void createUserAndSwitch(String userName) {
if (!mIsUserPickerClickable) {
return;
}
mIsUserPickerClickable = false;
mUserEventManager.createNewUser(userName, result -> {
if (result.isSuccess()) {
int newUserId = result.getUserId();
mUserEventManager.switchUser(newUserId, switchResult -> {
mIsUserPickerClickable = true;
refreshUsers();
});
} else {
mIsUserPickerClickable = true;
showCreateUserError(result.getErrorMessage());
}
});
}
9.2 推荐 CarUserManager 调用模型
不同产品分支的 CarUserManager API 封装可能略有差异。推荐将创建、切换、回调处理封装到 UserEventManager,避免 Activity 直接持有太多 CarService 细节。
java
void createNewUser(String name, CreateUserCallback callback) {
UserCreationRequest request = new UserCreationRequest.Builder()
.setName(name)
.setUserType(UserManager.USER_TYPE_FULL_SECONDARY)
.setAdmin(false)
.build();
mCarUserManager.createUser(request, Runnable::run, response -> {
if (response.isSuccess()) {
callback.onResult(CreateUserResult.success(
response.getUser().getIdentifier()));
} else {
callback.onResult(CreateUserResult.failure(
response.getStatus(), response.getErrorMessage()));
}
});
}
如果产品分支没有 CarUserManager.createUser(UserCreationRequest, Executor, Callback) 形式的公开接口,可在系统应用内使用 UserManager:
java
@RequiresPermission(android.Manifest.permission.MANAGE_USERS)
UserHandle createSecondaryUser(Context context, String name) {
UserManager userManager = context.getSystemService(UserManager.class);
UserInfo userInfo = userManager.createUser(
name,
UserManager.USER_TYPE_FULL_SECONDARY,
0 /* flags */);
if (userInfo == null) {
throw new IllegalStateException("createUser failed");
}
return UserHandle.of(userInfo.id);
}
9.3 新建后切换与 Display 绑定注意事项
java
void switchCreatedUser(int newUserId) {
mCarUserManager.switchUser(
new UserSwitchRequest.Builder(UserHandle.of(newUserId)).build(),
Runnable::run,
result -> {
if (result.isSuccess()) {
// 成功后等待 UserLifecycle / OccupantZone 更新,不建议立即强行启动 Launcher
} else {
Slogf.w(TAG, "switch user %d failed: %s",
newUserId, result);
}
});
}
注意:
- 若目标 Display 已有
UserPickerActivity,切换完成后 Home 是否启动仍受RootWindowContainer当前 Activity / focused root task 判断影响; - 多 Display 场景必须确认 user 与 Display/Occupant Zone 的绑定已同步;
- 新建 Guest 后如果产品策略要求一次性 Guest,应在 logout 或下次启动时处理替换;
- 如果需要下次默认启动该用户,需要确保 boot user 设置流程生效。
9.4 新建用户流程图
#mermaid-svg-gOif3uHmW6PlVLM3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gOif3uHmW6PlVLM3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gOif3uHmW6PlVLM3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gOif3uHmW6PlVLM3 .error-icon{fill:#552222;}#mermaid-svg-gOif3uHmW6PlVLM3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gOif3uHmW6PlVLM3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gOif3uHmW6PlVLM3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gOif3uHmW6PlVLM3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gOif3uHmW6PlVLM3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gOif3uHmW6PlVLM3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gOif3uHmW6PlVLM3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gOif3uHmW6PlVLM3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gOif3uHmW6PlVLM3 .marker.cross{stroke:#333333;}#mermaid-svg-gOif3uHmW6PlVLM3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gOif3uHmW6PlVLM3 p{margin:0;}#mermaid-svg-gOif3uHmW6PlVLM3 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-gOif3uHmW6PlVLM3 .cluster-label text{fill:#333;}#mermaid-svg-gOif3uHmW6PlVLM3 .cluster-label span{color:#333;}#mermaid-svg-gOif3uHmW6PlVLM3 .cluster-label span p{background-color:transparent;}#mermaid-svg-gOif3uHmW6PlVLM3 .label text,#mermaid-svg-gOif3uHmW6PlVLM3 span{fill:#333;color:#333;}#mermaid-svg-gOif3uHmW6PlVLM3 .node rect,#mermaid-svg-gOif3uHmW6PlVLM3 .node circle,#mermaid-svg-gOif3uHmW6PlVLM3 .node ellipse,#mermaid-svg-gOif3uHmW6PlVLM3 .node polygon,#mermaid-svg-gOif3uHmW6PlVLM3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gOif3uHmW6PlVLM3 .rough-node .label text,#mermaid-svg-gOif3uHmW6PlVLM3 .node .label text,#mermaid-svg-gOif3uHmW6PlVLM3 .image-shape .label,#mermaid-svg-gOif3uHmW6PlVLM3 .icon-shape .label{text-anchor:middle;}#mermaid-svg-gOif3uHmW6PlVLM3 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-gOif3uHmW6PlVLM3 .rough-node .label,#mermaid-svg-gOif3uHmW6PlVLM3 .node .label,#mermaid-svg-gOif3uHmW6PlVLM3 .image-shape .label,#mermaid-svg-gOif3uHmW6PlVLM3 .icon-shape .label{text-align:center;}#mermaid-svg-gOif3uHmW6PlVLM3 .node.clickable{cursor:pointer;}#mermaid-svg-gOif3uHmW6PlVLM3 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-gOif3uHmW6PlVLM3 .arrowheadPath{fill:#333333;}#mermaid-svg-gOif3uHmW6PlVLM3 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gOif3uHmW6PlVLM3 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gOif3uHmW6PlVLM3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gOif3uHmW6PlVLM3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-gOif3uHmW6PlVLM3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gOif3uHmW6PlVLM3 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-gOif3uHmW6PlVLM3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gOif3uHmW6PlVLM3 .cluster text{fill:#333;}#mermaid-svg-gOif3uHmW6PlVLM3 .cluster span{color:#333;}#mermaid-svg-gOif3uHmW6PlVLM3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-gOif3uHmW6PlVLM3 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gOif3uHmW6PlVLM3 rect.text{fill:none;stroke-width:0;}#mermaid-svg-gOif3uHmW6PlVLM3 .icon-shape,#mermaid-svg-gOif3uHmW6PlVLM3 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gOif3uHmW6PlVLM3 .icon-shape p,#mermaid-svg-gOif3uHmW6PlVLM3 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-gOif3uHmW6PlVLM3 .icon-shape .label rect,#mermaid-svg-gOif3uHmW6PlVLM3 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gOif3uHmW6PlVLM3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-gOif3uHmW6PlVLM3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-gOif3uHmW6PlVLM3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否/系统权限路径
否
是
UserPicker 点击 Add user
UserPickerController 校验点击状态
UserEventManager.createNewUser
使用 CarUserManager?
CarUserManager.createUser
UserManager.createUser
创建成功?
恢复 UI/提示失败
获取 newUserId
CarUserManager.switchUser
UserController 切换用户
UserLifecycle / Visibility / OccupantZone 同步
关闭 UserPicker 或显示目标用户 Home
10. 调试与验证建议
10.1 常用命令
查看当前用户:
bash
adb shell am get-current-user
adb shell cmd user list
切换用户:
bash
adb shell am switch-user <userId>
停止用户,验证 logout 触发 UserPicker 的链路:
bash
adb shell am stop-user <userId>
查看 Activity 栈和 Display:
bash
adb shell dumpsys activity activities
adb shell dumpsys activity containers
adb shell dumpsys window displays
查看 CarService 与 user 相关日志:
bash
adb logcat -s CarUserService CarOccupantZoneService CarActivityService UserController UserVisibilityMediator ActivityTaskManager WindowManager
10.2 建议增加的日志点
java
Slogf.i(TAG, "startUserPicker: displayId=%d, userId=%d, userPicker=%s",
displayId, userId, userPickerName);
java
Slogf.i(TAG, "onOccupantZoneConfigChanged: flags=0x%x", flags);
java
Slogf.i(TAG, "handleSwitchUser: targetUser=%d, isLogout=%b",
targetUser.getIdentifier(), isLogout);
java
Slogf.i(TAG, "onUserVisibilityChanged: userId=%d, visible=%b",
userId, visible);
11. 关键状态与触发条件对照
| 场景 | 关键触发 | 结果 |
|---|---|---|
| 冷启动无初始用户 | InitialUserSetter.hasValidInitialUser() == false |
创建 Admin/Secondary 用户并切换 |
| 冷启动有初始用户 | getInitialUser() 成功 |
切换到 initial/boot user |
| Display 没有绑定 user | Occupant Zone USER/DISPLAY config change | 拉起 UserPickerActivity |
| UserPicker 已显示 | Activity 栈存在可 resume Activity | Framework 不再拉起 Launcher |
| 状态栏切用户 | ProfileSwitcher.switchUser() |
CarUserService -> UserController 切换 |
| logout | CarUserManager.stopUser() |
stop user -> unassign display -> UserPicker |
| 新建用户 | createUser() 成功 |
切换或刷新 UserPicker |
12. 代码校验说明
本文件中的代码片段按以下规则整理:
- 已根据提供文档中的
android-15.0.0_r17路径和方法名统一类名、方法名、参数名; - 对 Java / XML / Bash 代码块执行了基础格式校验:
- Java 片段括号与大括号成对;
- XML 片段标签闭合;
- Bash 命令不包含非 ASCII 命令选项;
- 部分代码为流程参考或产品侧补充封装,例如
CreateUserCallback、CreateUserResult、refreshUsers()、showCreateUserError(),需要按实际项目已有封装替换; - Android Automotive 的公开/隐藏 API 会随分支、产品 overlay、权限模型变化。涉及
MANAGE_USERS、INTERACT_ACROSS_USERS、CarUserManager的代码,应以本地android-15.0.0_r17checkout 编译结果为准。
13. 排查 checklist
-
config_userPickerActivity是否配置为com.android.systemui/.car.userpicker.UserPickerActivity; -
CarUserService.init()是否注册mOccupantZoneCallback; -
isMultipleUsersOnMultipleDisplaysSupported(mUserManager)是否返回 true; - Logout 后
UserController是否执行unassignUserFromDisplayOnStop(); -
UserVisibilityMediator是否从 visible -> invisible 分发事件; -
CarServiceHelperService是否收到onUserVisibilityChanged(); -
CarOccupantZoneService是否发出ZONE_CONFIG_CHANGE_FLAG_USER; -
CarUserService.startUserPicker()是否被调用; -
CarActivityService.startUserPickerOnDisplay()是否成功读取配置并启动 Activity; - 如果 Launcher 未显示,检查该 Display 上是否已经存在
UserPickerActivity或 focused root task。
-----------------------------------------------------------End Line------------------------------------------------------------------