AOSP Expert & Production Engineering
3 min read

Multi-User Framework Internals

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, and Zygote.

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:

  1. Created: The user's metadata exists, and basic directories are formed, but no processes can run.
  2. Started: The user's system services are initialized. The ACTION_USER_STARTED broadcast is sent. However, the user's credential-encrypted (CE) storage is not yet unlocked.
  3. Unlocked: The user enters their PIN/password. The CE storage is decrypted. ACTION_USER_UNLOCKED is broadcast. Most apps wait for this state to access their data.
  4. Stopped: All processes for the user are killed, and the CE storage is locked. ACTION_USER_STOPPED is 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.