Android's multi-user framework allows multiple distinct user spaces to coexist on a single device, enabling features like work profiles, guest accounts, and shared devices. The framework heavily relies on UID and PID separation at the Linux kernel level.
UserManager and UserController Internals
The UserManager API exposes multi-user capabilities to applications, but the heavy lifting is done by the UserController within the ActivityManagerService (AMS) and the UserManagerService (UMS).
- UserManagerService (UMS): Manages the persistent state of users (creation, deletion, metadata, restrictions). User data is stored in
/data/system/users/. - UserController: Manages the runtime state of users. It handles the complex orchestration of starting, stopping, and switching users, coordinating with
PackageManagerService,WindowManagerService, andZygote.
When a new user is created, UMS allocates a new internal integer User ID (e.g., 0 for system, 10 for first secondary user).
// Creating a user via UserManager (requires system privileges)
UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
UserHandle newUser = userManager.createUser("Guest User", UserManager.USER_TYPE_FULL_GUEST, 0);
Android calculates the Application UID by combining the User ID and the App ID:
uid = (UserId * 100000) + AppId
For example, User 10 running App 10050 will have UID 1010050.
User Lifecycle: Created, Started, Unlocked, Stopped
A user goes through a strict lifecycle managed by UserController:
- Created: The user's metadata exists, and basic directories are formed, but no processes can run.
- Started: The user's system services are initialized. The
ACTION_USER_STARTEDbroadcast is sent. However, the user's credential-encrypted (CE) storage is not yet unlocked. - Unlocked: The user enters their PIN/password. The CE storage is decrypted.
ACTION_USER_UNLOCKEDis broadcast. Most apps wait for this state to access their data. - Stopped: All processes for the user are killed, and the CE storage is locked.
ACTION_USER_STOPPEDis broadcast.
To view the current state of all users on a device:
adb shell dumpsys user
Per-User Data Directories
Storage isolation is critical. Android creates separate directory structures for each user:
- Device Encrypted (DE) Storage:
/data/user_de/<user_id>/<package_name>/- Available as soon as the user is started, before unlock. Useful for alarm apps or device management. - Credential Encrypted (CE) Storage:
/data/user/<user_id>/<package_name>/- The default storage. Only accessible after the user unlocks their profile.
When an app calls Context.getFilesDir(), the framework automatically resolves it to the correct path based on the calling process's User ID.
Cross-User Communication Restrictions
By default, processes running in different users cannot communicate. Intents, ContentProviders, and Binder calls are strictly scoped to the current user.
To communicate across users, apps need the INTERACT_ACROSS_USERS or INTERACT_ACROSS_USERS_FULL signature-level permissions.
When sending an Intent, system apps can specify the target user:
Intent intent = new Intent("com.example.ACTION_SYNC_DATA");
// Sending broadcast to a specific user
context.sendBroadcastAsUser(intent, UserHandle.of(10));
The system enforces this via AMS, checking the calling UID's permissions before routing the Intent or binding the service across the user boundary.