Android 15 r17 CarUser / UserPickerActivity 启动、切换、退出与新建用户流程

Android 15 r17 CarUser / UserPickerActivity 启动、切换、退出与新建用户流程整理

本文是作者基于AOSP15_r17的源码进行整理,摘录代码后由AI整理文章格式和相关图示。
已确认代码和图示的正确性,请参考。

版本基线:android-15.0.0_r17

范围:AAOS / CarService / Car SystemUI 中与 CarUserServiceUserPickerActivity、User 启动、切换、退出、新建相关的主流程。

资料来源:1.txt 中记录的源码路径、调用点和排查结论。


1. 结论摘要

  1. UserPickerActivity 的启动入口不在 Launcher 流程中,而是由 Car 侧根据 Display 与 User 绑定关系触发。核心触发点是 CarUserService 注册的 ICarOccupantZoneCallback,当 Occupant Zone 的 Display 或 User 配置变化时调用 startUserPicker()
  2. UserPickerActivity 已经在某个 Display 上启动后,Framework 的 resumeTopActivities() 不会再无条件启动 Home/Launcher。RootWindowContainer 仅在当前 Display 没有可 resume 的 Activity,且没有 focused root task 时,才通过 resumeHomeActivity() 拉起 Home。
  3. 首次冷启动时,CarUserService.priorityInit() 会触发 Initial User 决策。若没有有效初始用户,InitialUserSetter 会创建一个 Admin 用户并切换;若已有有效初始用户,则切换到该用户。
  4. 下次开机默认回到上次关联/设置的 boot user,关键在 UserManagerService.setBootUser()HsumBootUserInitializer.systemRunning() 后续 switchToBootUser 流程。
  5. 开机后通过状态栏/用户列表切换 Guest、Profile 或普通用户时,入口通常经过 Car SystemUI 的 ProfileSwitcher,再调用 CarUserManager / CarUserService.switchUser()。切换过程中的 loading 画面由 UserSwitchTransitionViewController / UserSwitchTransitionViewMediator 控制。
  6. Logout 后重新显示 UserPickerActivity 的关键链路是:停止当前 user -> user 从 display/visibility/occupant zone 解绑 -> CarOccupantZoneService 发出 User 配置变化 -> CarUserService.mOccupantZoneCallback 收到变化 -> startUserPicker()
  7. 新建用户流程应放在 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_DISPLAYZONE_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 设计,新建用户建议遵循以下路径:

  1. UI 层从 UserPickerActivityUserPickerController 或独立用户管理入口发起新建;
  2. 使用 CarUserManager.createUser() 或 Framework UserManager.createUser() 创建普通用户、Guest 或 Admin;
  3. 创建成功后,按业务需要执行:
    • 分配 user 到当前 Display / Occupant Zone;
    • 切换到新 user;
    • 设置 boot user;
    • 刷新 UserPicker 列表或关闭 UserPicker;
  4. 如果创建失败,应返回明确失败码,并恢复 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. 代码校验说明

本文件中的代码片段按以下规则整理:

  1. 已根据提供文档中的 android-15.0.0_r17 路径和方法名统一类名、方法名、参数名;
  2. 对 Java / XML / Bash 代码块执行了基础格式校验:
    • Java 片段括号与大括号成对;
    • XML 片段标签闭合;
    • Bash 命令不包含非 ASCII 命令选项;
  3. 部分代码为流程参考或产品侧补充封装,例如 CreateUserCallbackCreateUserResultrefreshUsers()showCreateUserError(),需要按实际项目已有封装替换;
  4. Android Automotive 的公开/隐藏 API 会随分支、产品 overlay、权限模型变化。涉及 MANAGE_USERSINTERACT_ACROSS_USERSCarUserManager 的代码,应以本地 android-15.0.0_r17 checkout 编译结果为准。

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------------------------------------------------------------------

相关推荐
星空语10 小时前
音频001_Android+Linux车载/手机音频全链路分层架构图
android·linux·音视频
CyL_Cly10 小时前
解决官网下载慢!Android Studio 最新版网盘下载
android·ide·android studio
智塑未来10 小时前
鸿蒙系统对比安卓、iOS,核心优势是什么?
android·ios·harmonyos
AI刀刀10 小时前
豆包智能体对话导出后,如何构建长期归档与高效检索体系?
android·人工智能·word·excel·ai导出鸭
初雪云12 小时前
iOS 上架入门:证书、描述文件、IPA 上传到底是什么关系?
android·ios·自动化·产品经理·iphone
玩机达人8814 小时前
一加12/Ace5/Ace3pro手机ColorOS16降级回锁BL熔断变黑砖怎么办
android·智能手机·电脑
Digitally15 小时前
使用 USB 数据线将文件从安卓设备传输到电脑
android·电脑
xieliyu.16 小时前
MySQL 六大基础约束详解:NOT NULL/DEFAULT/UNIQUE/ 主键 / 外键 / CHECK
android·数据库·sql·mysql
知行合一。。。16 小时前
LangChain--06--结构化输出(Structured Output)
android·java·langchain
2501_9159184116 小时前
PerfDog 太贵?iOS 性能监控工具的功能对比与选择
android·ios·小程序·https·uni-app·iphone·webview