Android The multi-user in Windows is similar to the multi-user in Windows, which can support multiple users to use the system. Usually, the first user registered in the system will default to become a system administrator.
The settings of different users are different, and the application and application data installed by different users are also different. However, hardware-related settings in the system are common, such as network settings.
Android is different from Windows in that the background process run by the previous user can continue to run after the user switches. In this way, there is no need to interrupt some time-consuming operations in the background (downloaded below).
1. System Services for Managing Users--User Manager Service
The main function of UserManagerService is to create and delete users and query user information.
1. Initialization
UserManagerService is created in the construction method of PackageManagerService, as follows:
The construction method of UserManagerService is as follows:
- sUserManager = new UserManagerService(context, this, mInstallLock, mPackages);
Call another constructor and pass two more parameters: / data directory and / data/user file. As follows:
- UserManagerService(Context context, PackageManagerService pm,
- Object installLock, Object packagesLock) {
- this(context, pm, installLock, packagesLock,
- Environment.getDataDirectory(),
- new File(Environment.getDataDirectory(), "user"));
- }
Several directories are created in the construction method: / data/system/users, data/system/users/0, / data/system/users/userlist.xml, etc. After that, the basic restrictions are set as follows:
- private UserManagerService(Context context, PackageManagerService pm,
- Object installLock, Object packagesLock,
- File dataDir, File baseUserPath) {
- mContext = context;
- mPm = pm;
- mInstallLock = installLock;
- mPackagesLock = packagesLock;
- mHandler = new Handler();
- synchronized (mInstallLock) {
- synchronized (mPackagesLock) {
- mUsersDir = new File(dataDir, USER_INFO_DIR);// /data/system/users
- mUsersDir.mkdirs();
- // Make zeroth user directory, for services to migrate their files to that location
- File userZeroDir = new File(mUsersDir, "0");//The first user's directory / data/system/users/0
- userZeroDir.mkdirs();
- mBaseUserPath = baseUserPath;// /data/user
- FileUtils.setPermissions(mUsersDir.toString(),
- FileUtils.S_IRWXU|FileUtils.S_IRWXG
- |FileUtils.S_IROTH|FileUtils.S_IXOTH,
- -1, -1);
- mUserListFile = new File(mUsersDir, USER_LIST_FILENAME);// /data/system/users/userlist.xml
- initDefaultGuestRestrictions();
- readUserListLocked();//Analysis of user information files
- // Prune out any partially created/partially removed users.
- ArrayList<UserInfo> partials = new ArrayList<UserInfo>();
- for (int i = 0; i < mUsers.size(); i++) {
- UserInfo ui = mUsers.valueAt(i);
- if ((ui.partial || ui.guestToRemove) && i != 0) {
- partials.add(ui);//Find the user whose attribute partial is true in the userlist.xml file
- }
- }
- for (int i = 0; i < partials.size(); i++) {
- UserInfo ui = partials.get(i);
- Slog.w(LOG_TAG, "Removing partially created user #" + i
- + " (name=" + ui.name + ")");
- removeUserStateLocked(ui.id);//Attribute to partial represents the creation of an incomplete user, removed from the system
- }
- sInstance = this;
- }
- }
- }
Then the readUserListLocked() method is called to parse the / data/system/users/userlist.xml file, which saves the id information of all users in the system. The document reads as follows:
- private void initDefaultGuestRestrictions() {
- if (mGuestRestrictions.isEmpty()) {
- mGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, true);//"no_outgoing_calls"
- mGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true);//"no_sms"
- }
- }
After getting the ID information, the XML file which saves the user registration information is read and expressed by the number of the user id, such as 0.xml. The contents are as follows:
- <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
- <users nextSerialNumber="11" version="5">
- <guestRestrictions>
- <restrictions no_outgoing_calls="true" no_sms="true" />
- </guestRestrictions>
- <user id="0" />
- <user id="10" />
- </users>
After reading the user's xml file content, a UserInfo object will be created and initialized according to the file content to save information, and the object will be added to the mUsers list. If there is partial="true" in the attribute of a user in the xml file, it means that the user has not been created successfully and needs to be removed from the system.
- <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
- <user id="0" serialNumber="0" flags="19" created="0" lastLoggedIn="1436392413074">
- <name>Owner</name>
- <restrictions />
- </user>
This completes the initialization of the UserManagerService. The main task is to analyze the userlist.xml file and create the UserInfo object in the mUsers list.
2. Definition of UserInfo
In UserManagerService, the UserInfo object represents the user and is defined as follows:Note: User id is used to identify the user, and after deletion, its id will be assigned to the next new user to keep the id number continuous; while serialNumber is a number that will not be repeated in the device to uniquely identify a user. If you want to record information related to a user in the application, it is better to use serialNumber to represent the user rather than the user id.
- public class UserInfo implements Parcelable {
- /** 8 bits for user type User type*/
- public static final int FLAG_MASK_USER_TYPE = 0x000000FF;
- /**
- * Primary user. Only one user can have this flag set. Meaning of this
- * flag TBD.The logo of the primary user, usually the first user with an Id of 0
- */
- public static final int FLAG_PRIMARY = 0x00000001;
- /**
- * User with administrative privileges. Such a user can create and
- * delete users.admin User logo, with which users can be created and deleted
- */
- public static final int FLAG_ADMIN = 0x00000002;
- /**
- * Indicates a guest user that may be transient.guest user identity
- */
- public static final int FLAG_GUEST = 0x00000004;
- /**
- * Indicates the user has restrictions in privileges, Identify users with limited privileges, and the specific restricted functions are undetermined.
- * Exact meaning TBD. For instance, maybe they can't install apps or administer WiFi access pts.
- */
- public static final int FLAG_RESTRICTED = 0x00000008;
- /**
- * Indicates that this user has gone through its first-time initialization.Identify whether the user has been initialized
- */
- public static final int FLAG_INITIALIZED = 0x00000010;
- /**
- * Indicates that this user is a profile of another user, for example holding a users
- * corporate data.Identify whether the UserInfo is user information or a profile
- */
- public static final int FLAG_MANAGED_PROFILE = 0x00000020;
- /**
- * Indicates that this user is disabled.Identify whether the user has been "banned"
- */
- public static final int FLAG_DISABLED = 0x00000040;
- public static final int NO_PROFILE_GROUP_ID = -1;//Invalid value definition of profile group id
- public int id;//User id
- public int serialNumber;//User's serial number, no duplication
- public String name;//User name
- public String iconPath;//User's avatar path
- public int flags;//User's logo
- public long creationTime;//Time to create users
- public long lastLoggedInTime;//Time of last login
- public int profileGroupId;//User profile group id
- /** User is only partially created. */
- public boolean partial;//This flag is true, indicating that the user has not created it.
- public boolean guestToRemove;
3. Restriction
User Manager in Android 5.1 defines many user-related restrictions that allow administrators to give different users different permissions when creating users.
- public static final String DISALLOW_MODIFY_ACCOUNTS = "no_modify_accounts";//Restrict modification of user accounts
- public static final String DISALLOW_CONFIG_WIFI = "no_config_wifi";//Restrict WiFi Configuration
- public static final String DISALLOW_INSTALL_APPS = "no_install_apps";//Restrict the installation of app s
- public static final String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps";//Restrict unloading app
- public static final String DISALLOW_SHARE_LOCATION = "no_share_location";//Limit shared geographic location
- public static final String DISALLOW_INSTALL_UNKNOWN_SOURCES = "no_install_unknown_sources";//Limit installation of local applications
- public static final String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";//Restricted Bluetooth Configuration
- public static final String DISALLOW_USB_FILE_TRANSFER = "no_usb_file_transfer";//Restrict file transfer through usb
- public static final String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";//Restrict the security settings of configuration devices
- public static final String DISALLOW_REMOVE_USER = "no_remove_user";//Restrict deletion of users
- public static final String DISALLOW_DEBUGGING_FEATURES = "no_debugging_features";//Restrict the use of debugging functions
- public static final String DISALLOW_CONFIG_VPN = "no_config_vpn";//Restrict configuration of VPN
- public static final String DISALLOW_CONFIG_TETHERING = "no_config_tethering";//Restrict configuration Tether
- public static final String DISALLOW_FACTORY_RESET = "no_factory_reset";//Restrict factory settings
- public static final String DISALLOW_ADD_USER = "no_add_user";//Restrict the creation of users
- public static final String ENSURE_VERIFY_APPS = "ensure_verify_apps";//Check the app installed by this user
- public static final String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";//Restrict user configurations for cell broadcasting
- public static final String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks";//Restriction on configuring mobile networks
- public static final String DISALLOW_APPS_CONTROL = "no_control_apps";//Restrict the removal of application data and caches
- public static final String DISALLOW_MOUNT_PHYSICAL_MEDIA = "no_physical_media";//Restricted mount
- public static final String DISALLOW_UNMUTE_MICROPHONE = "no_unmute_microphone";//Restrict silencing of microphone
- public static final String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";//Limit volume
- public static final String DISALLOW_OUTGOING_CALLS = "no_outgoing_calls";//Restrict Calling
- public static final String DISALLOW_SMS = "no_sms";//Restrict sending SMS
- public static final String DISALLOW_CREATE_WINDOWS = "no_create_windows";//Limiting User's Application for Bullet Window Function
- public static final String DISALLOW_CROSS_PROFILE_COPY_PASTE = "no_cross_profile_copy_paste";//Restrict copying and pasting profile s
- public static final String DISALLOW_OUTGOING_BEAM = "no_outgoing_beam";//limit
4. Adding Users
The user interface added to the UserManagerService service is createUser(), as follows:
The createUser() method first checks the permissions of the caller, which can only be invoked by a process with system identity. Then call the createUserInternal() method to continue execution, as follows:
- public UserInfo createUser(String name, int flags) {
- checkManageUsersPermission("Only the system can create users");
- return createUserInternal(name, flags, UserHandle.USER_NULL);
- }
(1) Check whether the user to which the calling process belongs is restricted to adding users.
- private UserInfo createUserInternal(String name, int flags, int parentId) {
- if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(
- UserManager.DISALLOW_ADD_USER, false)) {//Get permission to add users
- Log.w(LOG_TAG, "Cannot add user. DISALLOW_ADD_USER is enabled.");
- return null;
- }
- final boolean isGuest = (flags & UserInfo.FLAG_GUEST) != 0;
- final long ident = Binder.clearCallingIdentity();
- UserInfo userInfo = null;
- try {
- synchronized (mInstallLock) {
- synchronized (mPackagesLock) {
- UserInfo parent = null;
- if (parentId != UserHandle.USER_NULL) {
- parent = getUserInfoLocked(parentId);
- if (parent == null) return null;
- }
- //If the Guest user is not added and reaches the upper limit, return
- if (!isGuest && isUserLimitReachedLocked()) {
- return null;
- }
- //If the Guest user is added, but the Guest user already exists, return
- if (isGuest && findCurrentGuestUserLocked() != null) {
- return null;
- }
- //
- if ((flags & UserInfo.FLAG_MANAGED_PROFILE) != 0
- && numberOfUsersOfTypeLocked(UserInfo.FLAG_MANAGED_PROFILE, true)
- >= MAX_MANAGED_PROFILES) {//At present, the maximum MAX_MANAGED_PROFILES is 1
- return null;//If a profile of a user is created, check that the user already has a profile
- }
- int userId = getNextAvailableIdLocked();//Get the new user ID
- userInfo = new UserInfo(userId, name, null, flags);
- File userPath = new File(mBaseUserPath, Integer.toString(userId));
- userInfo.serialNumber = mNextSerialNumber++;
- long now = System.currentTimeMillis();
- userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0;
- userInfo.partial = true;//Set the partial attribute to true
- Environment.getUserSystemDirectory(userInfo.id).mkdirs();
- mUsers.put(userId, userInfo);//Add new user information to mUsers
- writeUserListLocked();//Write user information to the file userlist.xml
- if (parent != null) {
- if (parent.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
- parent.profileGroupId = parent.id;
- writeUserLocked(parent);
- }
- userInfo.profileGroupId = parent.profileGroupId;
- }
- writeUserLocked(userInfo);//Create user information file 10.xml
- mPm.createNewUserLILPw(userId, userPath);
- userInfo.partial = false;//Create success, set partial to false
- writeUserLocked(userInfo);//Update the user's information file
- updateUserIdsLocked();
- Bundle restrictions = new Bundle();
- mUserRestrictions.append(userId, restrictions);
- }
- }
- if (userInfo != null) {//Broadcast users create successful messages
- Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
- addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id);
- mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
- android.Manifest.permission.MANAGE_USERS);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- return userInfo;
(2) Call the isUserLimitReachedLocked() method to determine whether more users can be created. The isUserLimitReachedLocked() method calls the getMaxSupportedUsers() method to compare the maximum number of users allowed by the system with the current number of users.
(3) Call the getNextAvailableIdLocked() method to get the new user ID. The getNextAvailableIdLocked() method looks up from 10 and returns a user ID if it is not already in use.
(4) Create user's data directory according to user id. UserInfo objects are created for users and added to the mUsers list.
(5) Add user id information to userlist.xml file. Create an XML file in the / data/system/users / directory, which is represented by the user id number, and save the user information in UserInfo. The partial attribute in the file will be set to true.
(6) Call the createNewuserLILPw() method of PackageManagerService to create a data directory for all applications under the directory of the new user.
(7) Update the user's information file and remove the partial attribute from the xml file so that the new user is created. The purpose of using partial attributes is to prevent errors in calling the createNewUserLILPw() method and failing to create users successfully. With this flag, users who failed to create can be cleared when the system restarts.
(8) Broadcast the message Intent.ACTION_USER_ADDED that joins the new user.
5. Guest users
In Android 5.0, creating Guest users needs to be done through the createGuest() method of UserManager. As follows:createGuest () first calls the createUser() method to create a Guest user, and then calls the setUserRestrictions() method to further restrict the Guest user. Therefore, the difference between Guest users and ordinary users is that they have different permissions.
- public UserInfo createGuest(Context context, String name) {
- UserInfo guest = createUser(name, UserInfo.FLAG_GUEST);//Creating Guest Users
- if (guest != null) {
- Settings.Secure.putStringForUser(context.getContentResolver(),
- Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id);
- try {//Restrict the privileges of Guest users
- Bundle guestRestrictions = mService.getDefaultGuestRestrictions();
- guestRestrictions.putBoolean(DISALLOW_SMS, true);
- guestRestrictions.putBoolean(DISALLOW_INSTALL_UNKNOWN_SOURCES, true);
- mService.setUserRestrictions(guestRestrictions, guest.id);
- } catch (RemoteException re) {
- Log.w(TAG, "Could not update guest restrictions");
- }
- }
- return guest;
- }
6. Delete Users
The removeUser() method is used to delete the user's interface in the UserManagerService service, as follows:
- public boolean removeUser(int userHandle) {
- checkManageUsersPermission("Only the system can remove users");
- if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(
- UserManager.DISALLOW_REMOVE_USER, false)) {
- Log.w(LOG_TAG, "Cannot remove user. DISALLOW_REMOVE_USER is enabled.");
- return false;
- }//Check the permissions of the calling process and whether the user to which the process belongs is "restricted"
- long ident = Binder.clearCallingIdentity();
- try {
- final UserInfo user;
- synchronized (mPackagesLock) {
- user = mUsers.get(userHandle);
- if (userHandle == 0 || user == null || mRemovingUserIds.get(userHandle)) {
- return false;
- }
- //Place userHandle in the mRemovingUserIds list to prevent duplicate deletion of a user
- mRemovingUserIds.put(userHandle, true);
- try {
- mAppOpsService.removeUser(userHandle);
- } catch (RemoteException e) {
- Log.w(LOG_TAG, "Unable to notify AppOpsService of removing user", e);
- }
- // Set this to a partially created user, so that the user will be purged
- // on next startup, in case the runtime stops now before stopping and
- // removing the user completely.
- user.partial = true;
- // Mark it as disabled, so that it isn't returned any more when
- // profiles are queried.
- user.flags |= UserInfo.FLAG_DISABLED;
- writeUserLocked(user);//Update user information file
- }
- if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
- && user.isManagedProfile()) {
- // Send broadcast to notify system that the user removed was a
- // managed user.
- sendProfileRemovedBroadcast(user.profileGroupId, user.id);
- }
- if (DBG) Slog.i(LOG_TAG, "Stopping user " + userHandle);
- int res;
- try {//Stop running users
- res = ActivityManagerNative.getDefault().stopUser(userHandle,
- new IStopUserCallback.Stub() {//callback
- @Override
- public void userStopped(int userId) {
- finishRemoveUser(userId);
- }
- @Override
- public void userStopAborted(int userId) {
- }
- });
- } catch (RemoteException e) {
- return false;
- }
- return res == ActivityManager.USER_OP_SUCCESS;
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
The removeUser() method does not delete user-related files immediately, but first changes the partial attribute of user information to true. The advantage is that if the subsequent process is terminated unexpectedly, all directories and files of the user will be deleted after the system restarts.
Considering that the deleted user may be running, the removeUser() method calls the stopUser() method of ActivityManagerService to change the user's running status. After that, AMS calls the callback method in the function parameter and finally calls the finishRemoveUser() method. As follows:
- void finishRemoveUser(final int userHandle) {
- if (DBG) Slog.i(LOG_TAG, "finishRemoveUser " + userHandle);
- // Let other services shutdown any activity and clean up their state before completely
- // wiping the user's system directory and removing from the user list
- long ident = Binder.clearCallingIdentity();
- try {
- Intent addedIntent = new Intent(Intent.ACTION_USER_REMOVED);
- addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userHandle);
- mContext.sendOrderedBroadcastAsUser(addedIntent, UserHandle.ALL,
- android.Manifest.permission.MANAGE_USERS,
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (DBG) {
- Slog.i(LOG_TAG,
- "USER_REMOVED broadcast sent, cleaning up user data "
- + userHandle);
- }
- new Thread() {
- public void run() {
- synchronized (mInstallLock) {
- synchronized (mPackagesLock) {
- removeUserStateLocked(userHandle);
- }
- }
- }
- }.start();
- }
- },
- null, Activity.RESULT_OK, null, null);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
In the finishRemoveUser() method, the broadcast Intent.ACTION_USER_REMOVED is issued, and a Broadcast Receiver object is created to receive the broadcast. Note that the broadcast receiver created at this time will finally receive the broadcast notification. This is in order to let other places in the system concerned about this broadcast process first, and finally return here to continue to complete the deletion of users. After receiving the broadcast, considering the time-consuming work ahead, a thread is started in the onReceiver() method to run the removeUserStateLocked() method. The code is as follows:
- private void removeUserStateLocked(final int userHandle) {
- //Cleanup package manager settings call the cleanUpUserLILPw method of PackageManagerService to delete app data from all user directories
- mPm.cleanUpUserLILPw(this, userHandle);
- //Remove this user from the list to delete the user's UserInfo object in the mUsers list
- mUsers.remove(userHandle);
- mRestrictionsPinStates.remove(userHandle);//Remove users from the mRestrictions PinStates list
- //Remove user file to delete user's information file
- AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + XML_SUFFIX));
- userFile.delete();
- // Update the user list
- writeUserListLocked();//Update the file userlists.xml to remove user information
- updateUserIdsLocked();
- removeDirectoryRecursive(Environment.getUserSystemDirectory(userHandle));//Delete all files in the user directory
- }
The removeUserStateLocked() method deletes all user-related files. First, the data file of app, then the user information file and the user Id information in userlist.xml. Finally, all the files in the user directory are deleted.
Package Manager Service and Multi-user
In a multi-user environment, all applications installed by users are still located in the / data/app directory, but the data of these applications is a separate copy under each user's directory / data / user /< user id >/. Directory / data/data also saves application data, but these data are only valid for users with id 0.
1. Creating User's Application Data
As mentioned above, the createNewUserLILPw() method of PackageManagerService is invoked when creating users, as follows:
The createNewUserLILPw() method simply calls mInstaller's createUserConfig() and mSettings'createNewUserLILPw() methods.
- /** Called by UserManagerService */
- void createNewUserLILPw(int userHandle, File path) {
- if (mInstaller != null) {
- mInstaller.createUserConfig(userHandle);
- mSettings.createNewUserLILPw(this, mInstaller, userHandle, path);
- }
- }
Let's first look at how mInstaller's createUserConfig() method handles it, as follows:
mInstaller is the object of InstallerConnection, where the execute() method of the InstallConnection object is called, as follows:
- public int createUserConfig(int userId) {
- StringBuilder builder = new StringBuilder("mkuserconfig");
- builder.append(' ');
- builder.append(userId);
- return mInstaller.execute(builder.toString());
- }
The process of communication with installd service is realized here, and the specific operation is completed by installd service. Looking at mSettings'createNewUserLILPw() method, we know that mSettings holds information about all applications read from files. As follows:
- public int execute(String cmd) {
- String res = transact(cmd);
- try {
- return Integer.parseInt(res);
- } catch (NumberFormatException ex) {
- return -1;
- }
- }
The createNewUserLILPw() method traverses the mPackages list, which stores all installed application information in the system. Then for each application, the installer's createUserData() method is invoked, and the Installer method eventually invokes the command of the installd daemon, where the installd "mkuserdata" command is finally invoked to perform the creation of the application data directory. Work.
- void createNewUserLILPw(PackageManagerService service, Installer installer,
- int userHandle, File path) {
- path.mkdir();//Create a directory / data/user for users to save application data
- FileUtils.setPermissions(path.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG
- | FileUtils.S_IXOTH, -1, -1);
- for (PackageSetting ps : mPackages.values()) {
- if (ps.pkg == null || ps.pkg.applicationInfo == null) {
- continue;
- }//Create a data directory for each application in the user's directory
- // Only system apps are initially installed.
- ps.setInstalled((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0, userHandle);
- // Need to create a data directory for all apps under this user.
- installer.createUserData(ps.name,
- UserHandle.getUid(userHandle, ps.appId), userHandle,
- ps.pkg.applicationInfo.seinfo);
- }
- readDefaultPreferredAppsLPw(service, userHandle);
- writePackageRestrictionsLPr(userHandle);
- }
At the same time, there is an array userState in each application's setup object PackageSetting, which indicates which users the application has been installed into. The setInstalled method calling ps here is to insert user information into the array userState.
- public int createUserData(String name, int uid, int userId, String seinfo) {
- StringBuilder builder = new StringBuilder("mkuserdata");
- builder.append(' ');
- builder.append(name);
- builder.append(' ');
- builder.append(uid);
- builder.append(' ');
- builder.append(userId);
- builder.append(' ');
- builder.append(seinfo != null ? seinfo : "!");
- return mInstaller.execute(builder.toString());
- }
The readDefault Preferred Apps LPw () method is called to analyze all xml files in the directory / etc/preferred-apps, where the xml files hold the most appropriate components specified by the device user to respond to an intent. Because each user can specify the most appropriate component it likes, each user needs to add its own Preferred IntentResolver object to the mPreferred Activities list, which stores the intent and the associated data of the component.
2. Delete User's Application Data
The cleanUpUserLILPw() method is used to delete users in PackageManagerService, as follows:
The main task of the cleanUpUserLILPw method is to call mInstaller's removeUserDataDirs() method to delete the data of all applications in the user directory. At the same time, the removeUserLPw() method of mSettings is called to delete the user-related data in PackageManagerService, as follows:
- /** Called by UserManagerService */
- void cleanUpUserLILPw(UserManagerService userManager, int userHandle) {
- mDirtyUsers.remove(userHandle);
- mSettings.removeUserLPw(userHandle);
- mPendingBroadcasts.remove(userHandle);
- if (mInstaller != null) {
- // Technically, we shouldn't be doing this with the package lock
- // held. However, this is very rare, and there is already so much
- // other disk I/O going on, that we'll let it slide for now.
- mInstaller.removeUserDataDirs(userHandle);//Delete all application data in user directory
- }
- mUserNeedsBadging.delete(userHandle);
- removeUnusedPackagesLILPw(userManager, userHandle);
- }
- void removeUserLPw(int userId) {
- Set<Entry<String, PackageSetting>> entries = mPackages.entrySet();
- for (Entry<String, PackageSetting> entry : entries) {
- entry.getValue().removeUser(userId);
- }<span style="font-family: Arial, Helvetica, sans-serif;">//Delete the user's information in each application information</span>
- mPreferredActivities.remove(userId);//Delete the user's data from the mPreferred Activities list
- File file = getUserPackagesStateFile(userId);
- file.delete();//Delete the package-restrictions.xml file in the user directory
- file = getUserPackagesStateBackupFile(userId);
- file.delete();
- removeCrossProfileIntentFiltersLPw(userId);
- }
Activity Manager Service and Multi-user
User Manager Service mainly manages user's account information, and user management in operation is implemented by Activity Manager Service.
1. User's Status
Users have four operating states, which are defined as follows:
In AMS, the variable mCurrentUserId records the current user Id, while the mStartedUsers list saves the running user. Only the users in the mStartedUsers list will have the four States listed above. Those who are not in the list are registered but not running users.
- public final class UserStartedState {
- // User is first coming up.
- public final static int STATE_BOOTING = 0;//User is booting
- // User is in the normal running state.
- public final static int STATE_RUNNING = 1;//The user is running
- // User is in the initial process of being stopped.
- public final static int STATE_STOPPING = 2;//Users are stopping
- // User is in the final phase of stopping, sending Intent.ACTION_SHUTDOWN.
- public final static int STATE_SHUTDOWN = 3;//User has been turned off
2. Switching Current Users
The interface switchUser() for switching current users is provided in Activity Manager Service as follows:switchUser() first calls the getUserInfo() method to get the user information corresponding to userId, and then uses the isManagedProfile() method to determine whether the UserInfo is just a profile(Android allows one user to have another profile). If it is indeed a user, the message START_USER_SWITCH_MSG is sent.
- public boolean switchUser(final int userId) {
- enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
- String userName;
- synchronized (this) {
- UserInfo userInfo = getUserManagerLocked().getUserInfo(userId);//Get the UserInfo corresponding to userId
- if (userInfo == null) {
- Slog.w(TAG, "No user info for user #" + userId);
- return false;
- }
- if (userInfo.isManagedProfile()) {//If this UserInfo is just another profile of User, exit
- Slog.w(TAG, "Cannot switch to User #" + userId + ": not a full user");
- return false;
- }
- userName = userInfo.name;
- mTargetUserId = userId;
- }
- mHandler.removeMessages(START_USER_SWITCH_MSG);
- mHandler.sendMessage(mHandler.obtainMessage(START_USER_SWITCH_MSG, userId, 0, userName));//Send messages to switched users
- return true;
- }
The processing of the message START_USER_SWITCH_MSG is to call the method showUserSwitchDialog, which will pop up a dialog box to determine whether to switch users. Once the user clicks the confirmation button, the startUserInForeground() method is called.
- case START_USER_SWITCH_MSG: {
- showUserSwitchDialog(msg.arg1, (String) msg.obj);
- break;
- }
- private void showUserSwitchDialog(int userId, String userName) {
- // The dialog will show and then initiate the user switch by calling startUserInForeground
- Dialog d = new UserSwitchingDialog(this, mContext, userId, userName,
- true /* above system */);
- d.show();
- }
The startUserInForeground() method is as follows:
In the startUserInForeground() method, only the startUser() method is called, as follows:
- /**
- * Start user, if its not already running, and bring it to foreground.
- */
- boolean startUserInForeground(final int userId, Dialog dlg) {
- boolean result = startUser(userId, /* foreground */ true);
- dlg.dismiss();
- return result;
- }
- private boolean startUser(final int userId, final boolean foreground) {
- if (checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
- != PackageManager.PERMISSION_GRANTED) {
- . . . . . . //Check caller privileges
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- synchronized (this) {
- final int oldUserId = mCurrentUserId;
- if (oldUserId == userId) {
- return true;//The current user is the user whose parameters are specified. Exit
- }
- mStackSupervisor.setLockTaskModeLocked(null, false, "startUser");
- final UserInfo userInfo = getUserManagerLocked().getUserInfo(userId);
- . . . . . . //Some error handling, returning false
- if (foreground) {
- mWindowManager.startFreezingScreen(R.anim.screen_user_exit,
- R.anim.screen_user_enter);
- }
- boolean needStart = false;
- //If the user has not started yet, the user needs to start first and switch the user status to be started
- if (mStartedUsers.get(userId) == null) {
- mStartedUsers.put(userId, new UserStartedState(new UserHandle(userId), false));
- updateStartedUserArrayLocked();
- needStart = true;
- }
- final Integer userIdInt = Integer.valueOf(userId);
- mUserLru.remove(userIdInt);
- mUserLru.add(userIdInt);//Adjust the user's position in the mUserLru list, with the current user at the bottom
- if (foreground) {//Foreground switching
- mCurrentUserId = userId;//Switching the current user
- mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
- updateCurrentProfileIdsLocked();
- mWindowManager.setCurrentUser(userId, mCurrentProfileIds);//Set up the current user in WMS
- mWindowManager.lockNow(null);//Lock screen
- } else {
- final Integer currentUserIdInt = Integer.valueOf(mCurrentUserId);
- updateCurrentProfileIdsLocked();
- mWindowManager.setCurrentProfileIds(mCurrentProfileIds);
- mUserLru.remove(currentUserIdInt);
- mUserLru.add(currentUserIdInt);
- }
- final UserStartedState uss = mStartedUsers.get(userId);
- // Make sure user is in the started state. If it is currently
- // stopping, we need to knock that off.
- if (uss.mState == UserStartedState.STATE_STOPPING) {
- //If the user's status is stopping, switch to running
- uss.mState = UserStartedState.STATE_RUNNING;
- updateStartedUserArrayLocked();
- needStart = true;
- } else if (uss.mState == UserStartedState.STATE_SHUTDOWN) {
- //If the user's status is positive or closed, switch to active
- uss.mState = UserStartedState.STATE_BOOTING;
- updateStartedUserArrayLocked();
- needStart = true;
- }
- if (uss.mState == UserStartedState.STATE_BOOTING) {
- //Send message SYSTEM_USER_START_MSG if the user's status is booting
- mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId, 0));
- }
- if (foreground) {
- mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId,
- oldUserId));
- mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
- mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
- mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
- oldUserId, userId, uss));
- mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
- oldUserId, userId, uss), USER_SWITCH_TIMEOUT);
- }
- if (needStart) {
- //If the user is not in the STATE_RUNNING state before handover, broadcast ACTION_USER_STARTED to the user
- Intent intent = new Intent(Intent.ACTION_USER_STARTED);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
- | Intent.FLAG_RECEIVER_FOREGROUND);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null, null, AppOpsManager.OP_NONE,
- false, false, MY_PID, Process.SYSTEM_UID, userId);
- }
- if ((userInfo.flags&UserInfo.FLAG_INITIALIZED) == 0) {
- if (userId != UserHandle.USER_OWNER) {//If the user has not been initialized, send the broadcast ACTION_USER_INITIALIZE to the user
- Intent intent = new Intent(Intent.ACTION_USER_INITIALIZE);
- intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- broadcastIntentLocked(null, null, intent, null,
- new IIntentReceiver.Stub() {
- public void performReceive(Intent intent, int resultCode,
- String data, Bundle extras, boolean ordered,
- boolean sticky, int sendingUser) {
- onUserInitialized(uss, foreground, oldUserId, userId);
- }
- }, 0, null, null, null, AppOpsManager.OP_NONE,
- true, false, MY_PID, Process.SYSTEM_UID,
- userId);
- uss.initializing = true;
- } else {//Otherwise, call the makeInitialized method of UserManagerService directly
- getUserManagerLocked().makeInitialized(userInfo.id);
- }
- }
- if (foreground) {
- if (!uss.initializing) {//If the user has been initialized, set it as a front-end user
- moveUserToForeground(uss, oldUserId, userId);
- }
- } else {//Otherwise, add users to mStartingBackgroundUser first
- mStackSupervisor.startBackgroundUserLocked(userId, uss);
- }
- if (needStart) {//If the user is not in STATE_RUNNING state, broadcast ACTION_USER_STARTING to all users
- Intent intent = new Intent(Intent.ACTION_USER_STARTING);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- broadcastIntentLocked(null, null, intent,
- null, new IIntentReceiver.Stub() {
- @Override
- public void performReceive(Intent intent, int resultCode, String data,
- Bundle extras, boolean ordered, boolean sticky, int sendingUser)
- throws RemoteException {
- }
- }, 0, null, null,
- INTERACT_ACROSS_USERS, AppOpsManager.OP_NONE,
- true, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
- }
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- return true;
- }
The startUser() method code is long. The method first determines the user's status, and if the user is not running, there will be no user information in the mStartedUsers list. Therefore, we need to create the object UserStartedState that represents the user's status, and then add it to the mStartedUsers list. You also need to adjust the user's position in the mUserLru list to the last item. The maximum number of users running simultaneously in the system is 3 (the current definition). The mUserLru list is to record the login order of users and stop the redundant users running when needed.
For users already in the mStarted Users list, first determine whether its state is STATE_RUNNING, if not, it needs to complete the state transition, if in STATE_STOPPING state, directly into STATE_RUNNING state, if in STATE_SHUTDOWN state, first into STATE_BOOTING state. Therefore, after the switchUser() method is finished, the user may be in STATE_BOOTING state. When will the user switch to STATE_RUNNING state again? A little later.
Switching users also includes setting the variable mCurrentUserId that records the current user id, calling the setCurrentUser() method of WMS to change the current user, locking the current screen, and so on.
In addition to these tasks, the most important work of the startUser() method is broadcasting and user-related Intent. Because user switching also involves many modules, such as wallpaper management, input method, account management module, they need to know that the current user has changed, and then make corresponding adjustments. Here we should pay attention to the objects of broadcasting, some for new users, some for old users, some for all users.
(1) If the user is not in STATE_RUNNING state before handover, broadcast ACTION_USER_STARTED to the user.
(2) If the user whose id is not zero has not been initialized, the broadcast ACTION_USER_INITIALIZE is sent to the user.
(3) Call sendUserSwitchBroadcastsLocked() method to send broadcast ACTION_USER_BACKGROUND to old users, broadcast ACTION_USER_FOREGROUND to new users, and broadcast ACTION_USER_SWUTCHED to all users.
(4) If the user is not in STATE_RUNNING state before handover, broadcast ACTION_USER_STARTING to all users.
In addition, two messages are sent in the startUser() method: REPORT_USER_SWITCH_MSG and USER_SWITCH_TIMEOUT_MSG. The REPORT_USER_SWITCH_MSG message processing method is dispatchUserSwitch, as follows:
- case REPORT_USER_SWITCH_MSG: {
- dispatchUserSwitch((UserStartedState) msg.obj, msg.arg1, msg.arg2);
- break;
- }
- void dispatchUserSwitch(final UserStartedState uss, final int oldUserId,
- final int newUserId) {
- final int N = mUserSwitchObservers.beginBroadcast();
- if (N > 0) {
- final IRemoteCallback callback = new IRemoteCallback.Stub() {
- int mCount = 0;
- @Override
- public void sendResult(Bundle data) throws RemoteException {
- synchronized (ActivityManagerService.this) {
- if (mCurUserSwitchCallback == this) {
- mCount++;//If you receive a call that returns the result, mCount++.
- if (mCount == N) {//If all the results are returned, send a message to continue processing
- sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
- }
- }
- }
- }
- };
- synchronized (this) {
- uss.switching = true;
- mCurUserSwitchCallback = callback;
- }
- for (int i=0; i<N; i++) {
- try {//Calling onUserSwitching Method for Observed Objects
- mUserSwitchObservers.getBroadcastItem(i).onUserSwitching(
- newUserId, callback);
- } catch (RemoteException e) {
- }
- }
- } else {//If there is no observation object, call sendContinueUserSwitchLocked directly
- synchronized (this) {
- sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
- }
- }
- mUserSwitchObservers.finishBroadcast();
- }
The main function of the dispatchUserSwitch() method is to call the onUserSwitching() interface of the IUserSwitchObserver object in the mUserSwitchObservers list. If a module in the system is interested in user switching, it can call the registerUserSwitchObserver() interface of AMS to register an observation object, which will be saved in the list of mUserSwitchObservers. In the processing of its callback interface onUserSwitching(), the module registering the observation object needs to notify AMS by calling the sendResult method of the passed parameter object callback. Let's look at the sendResult() method in the code above. Only when all registrants call the sendResult() method, mCount will eventually equal N. Then the sendContinueUserSwitchLocked method will be called to send the CONTINUE_USER_SWITCH_MSG message to continue switching users.
Let's take a look at the processing of USER_SWITCH_TIMEOUT_MSG message. The purpose of sending USER_SWITCH_TIMEOUT_MSG message is to prevent the processing time of ONTINUE_USER_SWITCH_MSG message from being too long. After all, only when all registrants have finished processing can they continue. Android 5.1 is different from previous versions. It will continue to send CONTINUE_USER_SWITCH_MSG messages and continue user switching. As follows:
- case USER_SWITCH_TIMEOUT_MSG: {
- timeoutUserSwitch((UserStartedState) msg.obj, msg.arg1, msg.arg2);
- break;
- }
- void timeoutUserSwitch(UserStartedState uss, int oldUserId, int newUserId) {
- synchronized (this) {
- Slog.w(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId);
- sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
- }
- }
- void sendContinueUserSwitchLocked(UserStartedState uss, int oldUserId, int newUserId) {
- mCurUserSwitchCallback = null;
- mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
- mHandler.sendMessage(mHandler.obtainMessage(CONTINUE_USER_SWITCH_MSG,
- oldUserId, newUserId, uss));
- }
- case CONTINUE_USER_SWITCH_MSG: {
- continueUserSwitch((UserStartedState) msg.obj, msg.arg1, msg.arg2);
- break;
- }
- void continueUserSwitch(UserStartedState uss, int oldUserId, int newUserId) {
- completeSwitchAndInitalize(uss, newUserId, false, true);
- }
The processing method of CONTINUE_USER_SWITCH_MSG message is continueUserSwitch(), which calls completeSwitchAndInitalize() method to continue processing. The result of processing is to call their onUserSwitchComplete() method on all observation objects in the mUserSwitchObservers list.
- void completeSwitchAndInitalize(UserStartedState uss, int newUserId,
- boolean clearInitializing, boolean clearSwitching) {
- boolean unfrozen = false;
- synchronized (this) {
- if (clearInitializing) {
- uss.initializing = false;
- getUserManagerLocked().makeInitialized(uss.mHandle.getIdentifier());
- }
- if (clearSwitching) {
- uss.switching = false;
- }
- if (!uss.switching && !uss.initializing) {
- mWindowManager.stopFreezingScreen();
- unfrozen = true;
- }
- }
- if (unfrozen) {
- final int N = mUserSwitchObservers.beginBroadcast();
- for (int i=0; i<N; i++) {
- try {
- mUserSwitchObservers.getBroadcastItem(i).onUserSwitchComplete(newUserId);
- } catch (RemoteException e) {
- }
- }
- mUserSwitchObservers.finishBroadcast();
- }
- stopGuestUserIfBackground();
- }
So far, the work on the startUser() method is complete. But there is another question: when will the user with STATE_BOOTING switch to STATE_RUNNING? In the activityIdleInternalLocked() method, there is a section of code as follows:
The activityIdleInternalLocked() method is called when the Activity enters the Idle state, which means that the user has completed the switch. The finishUserBoot() method is called for each user in the startingUsers list, as follows:
- for (int i = 0; i < startingUsers.size(); i++) {
- mService.finishUserSwitch(startingUsers.get(i));
- }
The finishUserSwitch() method completes two tasks. One is to call the method finishUserBoot() to switch the user whose state is still STATE_BOOTING to STATE_RUNNING, and send the broadcast ACTION_BOOT_COMPLETED to the user to indicate the end of the user's start-up. Another job is to stop redundant users. Beginning with item 0 of the mUserLru list, users in the state of STATE_RUNNING will stop as long as they are not the primary user or the current user, leaving only those in the state of STATE_RUNNING.
- void finishUserSwitch(UserStartedState uss) {
- synchronized (this) {
- finishUserBoot(uss);
- startProfilesLocked();
- int num = mUserLru.size();
- int i = 0;
- while (num > MAX_RUNNING_USERS && i < mUserLru.size()) {
- Integer oldUserId = mUserLru.get(i);
- UserStartedState oldUss = mStartedUsers.get(oldUserId);
- if (oldUss == null) {//Normally, null does not occur.
- // Shouldn't happen, but be sane if it does.
- mUserLru.remove(i);
- num--;
- continue;
- }
- if (oldUss.mState == UserStartedState.STATE_STOPPING
- || oldUss.mState == UserStartedState.STATE_SHUTDOWN) {
- //This user is already stopping, does't count. If the user has stopped, continue
- num--;
- i++;
- continue;
- }
- if (oldUserId == UserHandle.USER_OWNER || oldUserId == mCurrentUserId) {
- // Owner and current can't be stopped, but count as running.
- i++;//If it is the primary user or the current user, continue
- continue;
- }
- //This is a user to be stopped.
- stopUserLocked(oldUserId, null);
- num--;
- i++;
- }
- }
- }
3. Stop User Running
The stop user interface in Activity Manager Service is stopUser(). After checking the permissions of the calling process, this method calls the internal method stopUserLocked() to continue to stop the user's work. The stopUserLocked() code is as follows:
The stopUserLocked() method first determines whether the user requesting the stop is the current user, yes, and returns. It can be seen that the current running user can not stop, it must be switched to other users before stopping the user.
- private int stopUserLocked(final int userId, final IStopUserCallback callback) {
- if (DEBUG_MU) Slog.i(TAG_MU, "stopUserLocked userId=" + userId);
- if (mCurrentUserId == userId && mTargetUserId == UserHandle.USER_NULL) {
- return ActivityManager.USER_OP_IS_CURRENT;
- }//If the user requesting to stop is the current user, cannot stop, return
- final UserStartedState uss = mStartedUsers.get(userId);
- if (uss == null) {
- // User is not started, nothing to do... but we do need to
- // callback if requested.
- if (callback != null) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- try {
- callback.userStopped(userId);//Callback method userStopped
- } catch (RemoteException e) {
- }
- }
- });
- }
- return ActivityManager.USER_OP_SUCCESS;//0
- }
- if (callback != null) {
- uss.mStopCallbacks.add(callback);
- }
- if (uss.mState != UserStartedState.STATE_STOPPING
- && uss.mState != UserStartedState.STATE_SHUTDOWN) {
- uss.mState = UserStartedState.STATE_STOPPING;//Switch the user's status to stop
- updateStartedUserArrayLocked();//Update the mStartedUserArray array to store the UserStartedState object
- long ident = Binder.clearCallingIdentity();
- try {
- // We are going to broadcast ACTION_USER_STOPPING and then
- // once that is done send a final ACTION_SHUTDOWN and then
- // stop the user.
- final Intent stoppingIntent = new Intent(Intent.ACTION_USER_STOPPING);
- stoppingIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- stoppingIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- stoppingIntent.putExtra(Intent.EXTRA_SHUTDOWN_USERSPACE_ONLY, true);
- final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
- // This is the result receiver for the final shutdown broadcast.
- final IIntentReceiver shutdownReceiver = new IIntentReceiver.Stub() {
- @Override
- public void performReceive(Intent intent, int resultCode, String data,
- Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
- finishUserStop(uss);//Receive ACTION_SHUTDOWN broadcast and continue execution
- }
- };
- // This is the result receiver for the initial stopping broadcast.
- final IIntentReceiver stoppingReceiver = new IIntentReceiver.Stub() {
- @Override
- public void performReceive(Intent intent, int resultCode, String data,
- Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
- // On to the next.
- synchronized (ActivityManagerService.this) {
- if (uss.mState != UserStartedState.STATE_STOPPING) {
- // Whoops, we are being started back up. Abort, abort!
- return;
- }
- uss.mState = UserStartedState.STATE_SHUTDOWN;//Change user status after receiving ACTION_USER_STOPPING broadcast
- }
- mBatteryStatsService.noteEvent(
- BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
- Integer.toString(userId), userId);
- mSystemServiceManager.stopUser(userId);
- broadcastIntentLocked(null, null, shutdownIntent,
- null, shutdownReceiver, 0, null, null, null, AppOpsManager.OP_NONE,
- true, false, MY_PID, Process.SYSTEM_UID, userId);//Re-transmit broadcast shutdown Intent (Intent.ACTION_SHUTDOWN)
- }
- };
- // Kick things off.
- broadcastIntentLocked(null, null, stoppingIntent,
- null, stoppingReceiver, 0, null, null,
- INTERACT_ACROSS_USERS, AppOpsManager.OP_NONE,
- true, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);//Send Broadcast Stopping Intent (Intent. ACTION_USER_STOPPING)
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- return ActivityManager.USER_OP_SUCCESS;
- }
Next, it determines whether the user is running or not, post s a message without running, and callbacks in the parameters are invoked in the message processing method to end the processing.
If the user is already running, first switch the user's state to STATE_STOPPING, then broadcast a message ACTION_USER_STOPPING that the user is stopping. At the same time, the method also receives the broadcast. After receiving, switch the user's state to STATE_SHUTDOWN, then send a broadcast of ACTION_SHUTDOWN. The method also accepts the broadcast, and then calls finishUserStop() Methods Continue to process as follows:
The main work of finishUserStop() method is to delete users from mStartedUsers and mUserLru, and then send a broadcast Intent.ACTION_USER_STOPPED to notify a user that it has stopped. Next, call the callback method of UserManagerService to notify UserManagerService to process, and finally call the removeUserLocked() method to delete relevant information from the mStackSupervisor list.
- void finishUserStop(UserStartedState uss) {
- final int userId = uss.mHandle.getIdentifier();
- boolean stopped;
- ArrayList<IStopUserCallback> callbacks;
- synchronized (this) {
- callbacks = new ArrayList<IStopUserCallback>(uss.mStopCallbacks);
- if (mStartedUsers.get(userId) != uss) {
- stopped = false;
- } else if (uss.mState != UserStartedState.STATE_SHUTDOWN) {
- stopped = false;
- } else {
- stopped = true;
- //User can no longer run. Delete users from AMS user-managed data structures
- mStartedUsers.remove(userId);
- mUserLru.remove(Integer.valueOf(userId));
- updateStartedUserArrayLocked();
- // Clean up all state and processes associated with the user.
- // Kill all the processes for the user.
- forceStopUserLocked(userId, "finish user");//Send Broadcast Intent.ACTION_USER_STOPPED
- }
- // Explicitly remove the old information in mRecentTasks.
- removeRecentTasksForUserLocked(userId);//Clear the user-related Recent Task list
- }
- for (int i=0; i<callbacks.size(); i++) {
- try {//Callback method calling UserManagerService
- if (stopped) callbacks.get(i).userStopped(userId);
- else callbacks.get(i).userStopAborted(userId);
- } catch (RemoteException e) {
- }
- }
- if (stopped) {
- mSystemServiceManager.cleanupUser(userId);//Delete users from mStackSupervisor
- synchronized (this) {
- mStackSupervisor.removeUserLocked(userId);
- }
- }
- }