Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a4091c69 authored by Jordan Jozwiak's avatar Jordan Jozwiak Committed by Android (Google) Code Review
Browse files

Merge "Add removeUserOrSetEphemeral"

parents f446da3c c22af8e2
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -86,6 +86,7 @@ interface IUserManager {
    Bundle getApplicationRestrictionsForUser(in String packageName, int userId);
    void setDefaultGuestRestrictions(in Bundle restrictions);
    Bundle getDefaultGuestRestrictions();
    int removeUserOrSetEphemeral(int userId);
    boolean markGuestForDeletion(int userId);
    UserInfo findCurrentGuestUser();
    boolean isQuietModeEnabled(int userId);
+17 −0
Original line number Diff line number Diff line
@@ -3977,6 +3977,23 @@ public class UserManager {
        }
    }

    /**
     * Immediately removes the user or, if the user cannot be removed, such as when the user is
     * the current user, then set the user as ephemeral so that it will be removed when it is
     * stopped.
     *
     * @return the {@link com.android.server.pm.UserManagerService.RemoveResult} code
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
    public int removeUserOrSetEphemeral(@UserIdInt int userId) {
        try {
            return mService.removeUserOrSetEphemeral(userId);
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
    }

    /**
     * Updates the user's name.
     *
+47 −3
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATIO
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;

import android.accounts.IAccountManager;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.role.IRoleManager;
@@ -2686,9 +2687,18 @@ class PackageManagerShellCommand extends ShellCommand {
        }
    }

    // pm remove-user [--set-ephemeral-if-in-use] USER_ID
    public int runRemoveUser() throws RemoteException {
        int userId;
        String arg = getNextArg();
        String arg;
        boolean setEphemeralIfInUse = false;
        while ((arg = getNextOption()) != null) {
            if (arg.equals("--set-ephemeral-if-in-use")) {
                setEphemeralIfInUse = true;
            }
        }

        arg = getNextArg();
        if (arg == null) {
            getErrPrintWriter().println("Error: no user id specified.");
            return 1;
@@ -2696,6 +2706,15 @@ class PackageManagerShellCommand extends ShellCommand {
        userId = UserHandle.parseUserArg(arg);
        IUserManager um = IUserManager.Stub.asInterface(
                ServiceManager.getService(Context.USER_SERVICE));
        if (setEphemeralIfInUse) {
            return removeUserOrSetEphemeral(um, userId);
        } else {
            return removeUser(um, userId);
        }
    }

    private int removeUser(IUserManager um, @UserIdInt int userId) throws RemoteException {
        Slog.i(TAG, "Removing user " + userId);
        if (um.removeUser(userId)) {
            getOutPrintWriter().println("Success: removed user");
            return 0;
@@ -2705,6 +2724,27 @@ class PackageManagerShellCommand extends ShellCommand {
        }
    }

    private int removeUserOrSetEphemeral(IUserManager um, @UserIdInt int userId)
            throws RemoteException {
        Slog.i(TAG, "Removing " + userId + " or set as ephemeral if in use.");
        int result = um.removeUserOrSetEphemeral(userId);
        switch (result) {
            case UserManagerService.REMOVE_RESULT_REMOVED:
                getOutPrintWriter().printf("Success: user %d removed\n", userId);
                return 0;
            case UserManagerService.REMOVE_RESULT_SET_EPHEMERAL:
                getOutPrintWriter().printf("Success: user %d set as ephemeral\n", userId);
                return 0;
            case UserManagerService.REMOVE_RESULT_ALREADY_BEING_REMOVED:
                getOutPrintWriter().printf("Success: user %d is already being removed\n", userId);
                return 0;
            default:
                getErrPrintWriter().printf("Error: couldn't remove or mark ephemeral user id %d\n",
                        userId);
                return 1;
        }
    }

    public int runSetUserRestriction() throws RemoteException {
        int userId = UserHandle.USER_SYSTEM;
        String opt = getNextOption();
@@ -3769,9 +3809,13 @@ class PackageManagerShellCommand extends ShellCommand {
        pw.println("      --restricted is shorthand for '--user-type android.os.usertype.full.RESTRICTED'.");
        pw.println("      --guest is shorthand for '--user-type android.os.usertype.full.GUEST'.");
        pw.println("");
        pw.println("  remove-user USER_ID");
        pw.println("  remove-user [--set-ephemeral-if-in-use] USER_ID");
        pw.println("    Remove the user with the given USER_IDENTIFIER, deleting all data");
        pw.println("    associated with that user");
        pw.println("    associated with that user.");
        pw.println("      --set-ephemeral-if-in-use: If the user is currently running and");
        pw.println("        therefore cannot be removed immediately, mark the user as ephemeral");
        pw.println("        so that it will be automatically removed when possible (after user");
        pw.println("        switch or reboot)");
        pw.println("");
        pw.println("  set-user-restriction [--user USER_ID] RESTRICTION VALUE");
        pw.println("");
+123 −9
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import android.Manifest;
import android.annotation.ColorRes;
import android.annotation.DrawableRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringRes;
@@ -110,7 +111,6 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
import com.android.server.SystemService;
import com.android.server.SystemService.TargetUser;
import com.android.server.am.UserState;
import com.android.server.storage.DeviceStorageMonitorInternal;
import com.android.server.utils.TimingsTraceAndSlog;
@@ -132,6 +132,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
@@ -246,6 +248,43 @@ public class UserManagerService extends IUserManager.Stub {
    static final int WRITE_USER_MSG = 1;
    static final int WRITE_USER_DELAY = 2*1000;  // 2 seconds

    /**
     * A response code from {@link #removeUserOrSetEphemeral(int)} indicating that the specified
     * user has been successfully removed.
     */
    public static final int REMOVE_RESULT_REMOVED = 0;

    /**
     * A response code from {@link #removeUserOrSetEphemeral(int)} indicating that the specified
     * user has had its {@link UserInfo#FLAG_EPHEMERAL} flag set to {@code true}, so that it will be
     * removed when the user is stopped or on boot.
     */
    public static final int REMOVE_RESULT_SET_EPHEMERAL = 1;

    /**
     * A response code from {@link #removeUserOrSetEphemeral(int)} indicating that the specified
     * user is already in the process of being removed.
     */
    public static final int REMOVE_RESULT_ALREADY_BEING_REMOVED = 2;

    /**
     * A response code from {@link #removeUserOrSetEphemeral(int)} indicating that an error occurred
     * that prevented the user from being removed or set as ephemeral.
     */
    public static final int REMOVE_RESULT_ERROR = 3;

    /**
     * Possible response codes from {@link #removeUserOrSetEphemeral(int)}.
     */
    @IntDef(prefix = { "REMOVE_RESULT_" }, value = {
            REMOVE_RESULT_REMOVED,
            REMOVE_RESULT_SET_EPHEMERAL,
            REMOVE_RESULT_ALREADY_BEING_REMOVED,
            REMOVE_RESULT_ERROR,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface RemoveResult {}

    // Tron counters
    private static final String TRON_GUEST_CREATED = "users_guest_created";
    private static final String TRON_USER_CREATED = "users_user_created";
@@ -253,9 +292,17 @@ public class UserManagerService extends IUserManager.Stub {

    private final Context mContext;
    private final PackageManagerService mPm;

    /**
     * Lock for packages. If using with {@link #mUsersLock}, {@link #mPackagesLock} should be
     * acquired first.
     */
    private final Object mPackagesLock;
    private final UserDataPreparer mUserDataPreparer;
    // Short-term lock for internal state, when interaction/sync with PM is not required
    /**
     * Short-term lock for internal state, when interaction/sync with PM is not required. If using
     * with {@link #mPackagesLock}, {@link #mPackagesLock} should be acquired first.
     */
    private final Object mUsersLock = LockGuard.installNewLock(LockGuard.INDEX_USER);
    private final Object mRestrictionsLock = new Object();
    // Used for serializing access to app restriction files
@@ -3862,13 +3909,7 @@ public class UserManagerService extends IUserManager.Stub {
        Slog.i(LOG_TAG, "removeUser u" + userId);
        checkManageOrCreateUsersPermission("Only the system can remove users");

        final boolean isManagedProfile;
        synchronized (mUsersLock) {
            UserInfo userInfo = getUserInfoLU(userId);
            isManagedProfile = userInfo != null && userInfo.isManagedProfile();
        }
        String restriction = isManagedProfile
                ? UserManager.DISALLOW_REMOVE_MANAGED_PROFILE : UserManager.DISALLOW_REMOVE_USER;
        final String restriction = getUserRemovalRestriction(userId);
        if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(restriction, false)) {
            Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled.");
            return false;
@@ -3882,6 +3923,21 @@ public class UserManagerService extends IUserManager.Stub {
        return removeUserUnchecked(userId);
    }

    /**
     * Returns the string name of the restriction to check for user removal. The restriction name
     * varies depending on whether the user is a managed profile.
     */
    private String getUserRemovalRestriction(@UserIdInt int userId) {
        final boolean isManagedProfile;
        final UserInfo userInfo;
        synchronized (mUsersLock) {
            userInfo = getUserInfoLU(userId);
        }
        isManagedProfile = userInfo != null && userInfo.isManagedProfile();
        return isManagedProfile
                ? UserManager.DISALLOW_REMOVE_MANAGED_PROFILE : UserManager.DISALLOW_REMOVE_USER;
    }

    private boolean removeUserUnchecked(@UserIdInt int userId) {
        final long ident = Binder.clearCallingIdentity();
        try {
@@ -3974,6 +4030,64 @@ public class UserManagerService extends IUserManager.Stub {
        }
    }

    @Override
    public @RemoveResult int removeUserOrSetEphemeral(@UserIdInt int userId) {
        Slog.i(LOG_TAG, "removeUserOrSetEphemeral u" + userId);
        checkManageUsersPermission("Only the system can remove users");
        final String restriction = getUserRemovalRestriction(userId);
        if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(restriction, false)) {
            Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled.");
            return REMOVE_RESULT_ERROR;
        }
        if (userId == UserHandle.USER_SYSTEM) {
            Slog.e(LOG_TAG, "System user cannot be removed.");
            return REMOVE_RESULT_ERROR;
        }

        final long ident = Binder.clearCallingIdentity();
        try {
            final UserData userData;
            synchronized (mPackagesLock) {
                synchronized (mUsersLock) {
                    userData = mUsers.get(userId);
                    if (userData == null) {
                        Slog.e(LOG_TAG,
                                "Cannot remove user " + userId + ", invalid user id provided.");
                        return REMOVE_RESULT_ERROR;
                    }

                    if (mRemovingUserIds.get(userId)) {
                        Slog.e(LOG_TAG, "User " + userId + " is already scheduled for removal.");
                        return REMOVE_RESULT_ALREADY_BEING_REMOVED;
                    }
                }

                // Attempt to immediately remove a non-current user
                final int currentUser = ActivityManager.getCurrentUser();
                if (currentUser != userId) {
                    // Attempt to remove the user. This will fail if the user is the current user
                    if (removeUser(userId)) {
                        return REMOVE_RESULT_REMOVED;
                    }

                    Slog.w(LOG_TAG, "Unable to immediately remove non-current user: " + userId
                            + ". User is still set as ephemeral and will be removed on user "
                            + "switch or reboot.");
                }

                // If the user was not immediately removed, make sure it is marked as ephemeral.
                // Don't mark as disabled since, per UserInfo.FLAG_DISABLED documentation, an
                // ephemeral user should only be marked as disabled when its removal is in progress.
                userData.info.flags |= UserInfo.FLAG_EPHEMERAL;
                writeUserLP(userData);

                return REMOVE_RESULT_SET_EPHEMERAL;
            }
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

    void finishRemoveUser(final @UserIdInt int userId) {
        if (DBG) Slog.i(LOG_TAG, "finishRemoveUser " + userId);

+106 −20
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Slog;

import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;

@@ -58,6 +59,8 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import javax.annotation.concurrent.GuardedBy;

/** Test {@link UserManager} functionality. */
@RunWith(AndroidJUnit4.class)
public final class UserManagerTest {
@@ -134,7 +137,7 @@ public final class UserManagerTest {
    @SmallTest
    @Test
    public void testHasSystemUser() throws Exception {
        assertThat(findUser(UserHandle.USER_SYSTEM)).isTrue();
        assertThat(hasUser(UserHandle.USER_SYSTEM)).isTrue();
    }

    @MediumTest
@@ -164,9 +167,9 @@ public final class UserManagerTest {
        assertThat(user1).isNotNull();
        assertThat(user2).isNotNull();

        assertThat(findUser(UserHandle.USER_SYSTEM)).isTrue();
        assertThat(findUser(user1.id)).isTrue();
        assertThat(findUser(user2.id)).isTrue();
        assertThat(hasUser(UserHandle.USER_SYSTEM)).isTrue();
        assertThat(hasUser(user1.id)).isTrue();
        assertThat(hasUser(user2.id)).isTrue();
    }

    @MediumTest
@@ -175,7 +178,7 @@ public final class UserManagerTest {
        UserInfo userInfo = createUser("Guest 1", UserInfo.FLAG_GUEST);
        removeUser(userInfo.id);

        assertThat(findUser(userInfo.id)).isFalse();
        assertThat(hasUser(userInfo.id)).isFalse();
    }

    @MediumTest
@@ -199,7 +202,7 @@ public final class UserManagerTest {
            }
        }

        assertThat(findUser(userInfo.id)).isFalse();
        assertThat(hasUser(userInfo.id)).isFalse();
    }

    @MediumTest
@@ -208,6 +211,79 @@ public final class UserManagerTest {
        assertThrows(IllegalArgumentException.class, () -> mUserManager.removeUser(null));
    }

    @MediumTest
    @Test
    public void testRemoveUserOrSetEphemeral_restrictedReturnsError() throws Exception {
        final int currentUser = ActivityManager.getCurrentUser();
        final UserInfo user1 = createUser("User 1", /* flags= */ 0);
        mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ true,
                asHandle(currentUser));
        try {
            assertThat(mUserManager.removeUserOrSetEphemeral(user1.id)).isEqualTo(
                    UserManagerService.REMOVE_RESULT_ERROR);
        } finally {
            mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ false,
                    asHandle(currentUser));
        }

        assertThat(hasUser(user1.id)).isTrue();
        assertThat(getUser(user1.id).isEphemeral()).isFalse();
    }

    @MediumTest
    @Test
    public void testRemoveUserOrSetEphemeral_systemUserReturnsError() throws Exception {
        assertThat(mUserManager.removeUserOrSetEphemeral(UserHandle.USER_SYSTEM)).isEqualTo(
                UserManagerService.REMOVE_RESULT_ERROR);

        assertThat(hasUser(UserHandle.USER_SYSTEM)).isTrue();
    }

    @MediumTest
    @Test
    public void testRemoveUserOrSetEphemeral_invalidUserReturnsError() throws Exception {
        assertThat(hasUser(Integer.MAX_VALUE)).isFalse();
        assertThat(mUserManager.removeUserOrSetEphemeral(Integer.MAX_VALUE)).isEqualTo(
                UserManagerService.REMOVE_RESULT_ERROR);
    }

    @MediumTest
    @Test
    public void testRemoveUserOrSetEphemeral_currentUserSetEphemeral() throws Exception {
        final int startUser = ActivityManager.getCurrentUser();
        final UserInfo user1 = createUser("User 1", /* flags= */ 0);
        // Switch to the user just created.
        switchUser(user1.id, null, /* ignoreHandle= */ true);

        assertThat(mUserManager.removeUserOrSetEphemeral(user1.id)).isEqualTo(
                UserManagerService.REMOVE_RESULT_SET_EPHEMERAL);

        assertThat(hasUser(user1.id)).isTrue();
        assertThat(getUser(user1.id).isEphemeral()).isTrue();

        // Switch back to the starting user.
        switchUser(startUser, null, /* ignoreHandle= */ true);

        // User is removed once switch is complete
        synchronized (mUserRemoveLock) {
            waitForUserRemovalLocked(user1.id);
        }
        assertThat(hasUser(user1.id)).isFalse();
    }

    @MediumTest
    @Test
    public void testRemoveUserOrSetEphemeral_nonCurrentUserRemoved() throws Exception {
        final UserInfo user1 = createUser("User 1", /* flags= */ 0);
        synchronized (mUserRemoveLock) {
            assertThat(mUserManager.removeUserOrSetEphemeral(user1.id)).isEqualTo(
                    UserManagerService.REMOVE_RESULT_REMOVED);
            waitForUserRemovalLocked(user1.id);
        }

        assertThat(hasUser(user1.id)).isFalse();
    }

    /** Tests creating a FULL user via specifying userType. */
    @MediumTest
    @Test
@@ -608,15 +684,20 @@ public final class UserManagerTest {
                () -> mUserManager.getUserCreationTime(asHandle(user.id)));
    }

    private boolean findUser(int id) {
    @Nullable
    private UserInfo getUser(int id) {
        List<UserInfo> list = mUserManager.getUsers();

        for (UserInfo user : list) {
            if (user.id == id) {
                return true;
                return user;
            }
        }
        return false;
        return null;
    }

    private boolean hasUser(int id) {
        return getUser(id) != null;
    }

    @MediumTest
@@ -918,6 +999,12 @@ public final class UserManagerTest {
    private void removeUser(int userId) {
        synchronized (mUserRemoveLock) {
            mUserManager.removeUser(userId);
            waitForUserRemovalLocked(userId);
        }
    }

    @GuardedBy("mUserRemoveLock")
    private void waitForUserRemovalLocked(int userId) {
        long time = System.currentTimeMillis();
        while (mUserManager.getUserInfo(userId) != null) {
            try {
@@ -931,7 +1018,6 @@ public final class UserManagerTest {
            }
        }
    }
    }

    private UserInfo createUser(String name, int flags) {
        UserInfo user = mUserManager.createUser(name, flags);