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

Commit 5e4e4a9a authored by Anna Bauza's avatar Anna Bauza
Browse files

Add caching for UserManager.hasUserRestriction()

This CL adds caching for UserManager.hasUserRestriction() to improve performance. The cache is invalidated when the user restrictions are changed. 44% of binder calls from uncached API belong to hasUserRestriction.

Bug: 350419621
Change-Id: I3a273b7296006adf33a0c7fbbf514b3c475e72f9
Test: atest com.android.server.pm.UserManagerCacheTest
Flag: android.multiuser.cache_user_restrictions_read_only
parent 3fd36128
Loading
Loading
Loading
Loading
+16 −6
Original line number Diff line number Diff line
@@ -4197,12 +4197,21 @@ public class UserManager {
            android.Manifest.permission.MANAGE_USERS,
            android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
    private boolean hasUserRestrictionForUser(@NonNull @UserRestrictionKey String restrictionKey,
            @UserIdInt int userId) {
        try {
            return mService.hasUserRestriction(restrictionKey, userId);
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
            @NonNull @UserIdInt int userId) {
        return getUserRestrictionFromQuery(new Pair(restrictionKey, userId));
    }

    /** @hide */
    @CachedProperty()
    private boolean getUserRestrictionFromQuery(@NonNull Pair<String, Integer> restrictionPerUser) {
        return UserManagerCache.getUserRestrictionFromQuery(
                (Pair<String, Integer> q) -> mService.hasUserRestriction(q.first, q.second),
                restrictionPerUser);
    }

    /** @hide */
    public static final void invalidateUserRestriction() {
        UserManagerCache.invalidateUserRestrictionFromQuery();
    }

    /**
@@ -6477,6 +6486,7 @@ public class UserManager {
            UserManagerCache.invalidateProfileParent();
        }
        invalidateEnabledProfileIds();
        invalidateUserRestriction();
    }

    /**
+8 −0
Original line number Diff line number Diff line
@@ -65,6 +65,7 @@ public class RestrictionsSet {
            throw new IllegalArgumentException("empty restriction bundle cannot be added.");
        }
        mUserRestrictions.put(userId, restrictions);
        UserManager.invalidateUserRestriction();
    }

    /**
@@ -84,6 +85,7 @@ public class RestrictionsSet {
        } else {
            mUserRestrictions.delete(userId);
        }
        UserManager.invalidateUserRestriction();
        return true;
    }

@@ -102,6 +104,9 @@ public class RestrictionsSet {
                removed = true;
            }
        }
        if (removed) {
            UserManager.invalidateUserRestriction();
        }
        return removed;
    }

@@ -129,6 +134,7 @@ public class RestrictionsSet {
                    i--;
                }
            }
            UserManager.invalidateUserRestriction();
        }
    }

@@ -192,6 +198,7 @@ public class RestrictionsSet {
    public boolean remove(@UserIdInt int userId) {
        boolean hasUserRestriction = mUserRestrictions.contains(userId);
        mUserRestrictions.remove(userId);
        UserManager.invalidateUserRestriction();
        return hasUserRestriction;
    }

@@ -200,6 +207,7 @@ public class RestrictionsSet {
     */
    public void removeAllRestrictions() {
        mUserRestrictions.clear();
        UserManager.invalidateUserRestriction();
    }

    /**
+1 −0
Original line number Diff line number Diff line
@@ -1113,6 +1113,7 @@ public class UserManagerService extends IUserManager.Stub {
                UserManager.invalidateUserPropertiesCache();
            }
            UserManager.invalidateCacheOnUserListChange();
            UserManager.invalidateUserRestriction();
        }
    }

+172 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.server.pm;
import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assume.assumeTrue;
import static org.junit.Assert.fail;

import android.app.ActivityManager;
import android.app.LocaleManager;
@@ -26,6 +27,7 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.multiuser.Flags;
import android.os.Bundle;
import android.os.LocaleList;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -261,6 +263,162 @@ public final class UserManagerCacheTest {
        assertThat(um.getUserName()).isEqualTo(newName);
    }


    @MediumTest
    @Test
    public void testDefaultRestrictionsApplied() throws Exception {
        final UserInfo userInfo = mUserManager.createUser("Useroid",
                UserManager.USER_TYPE_FULL_SECONDARY, 0);
        mUsersToRemove.add(userInfo.id);
        final UserTypeDetails userTypeDetails =
                UserTypeFactory.getUserTypes().get(UserManager.USER_TYPE_FULL_SECONDARY);
        final Bundle expectedRestrictions = userTypeDetails.getDefaultRestrictions();
        // Note this can fail if DO unset those restrictions.
        for (String restriction : expectedRestrictions.keySet()) {
            if (expectedRestrictions.getBoolean(restriction)) {
                assertThat(mUserManager.hasUserRestriction(restriction, UserHandle.of(userInfo.id)))
                        .isTrue();
                // Test cached value
                assertThat(mUserManager.hasUserRestriction(restriction, UserHandle.of(userInfo.id)))
                        .isTrue();
            }
        }
    }

    @MediumTest
    @Test
    public void testSetDefaultGuestRestrictions() {
        final Bundle origRestrictions = mUserManager.getDefaultGuestRestrictions();
        try {
            final boolean isFunDisallowed = origRestrictions.getBoolean(UserManager.DISALLOW_FUN,
                    false);
            final UserInfo guest1 = mUserManager.createUser("Guest 1", UserInfo.FLAG_GUEST);
            assertThat(guest1).isNotNull();
            assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
                    guest1.getUserHandle())).isEqualTo(isFunDisallowed);
            removeUser(guest1.id, true);
            // Cache return false after user was removed
            assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
                    guest1.getUserHandle())).isFalse();

            Bundle restrictions = new Bundle();
            restrictions.putBoolean(UserManager.DISALLOW_FUN, !isFunDisallowed);
            mUserManager.setDefaultGuestRestrictions(restrictions);
            UserInfo guest2 = mUserManager.createUser("Guest 2", UserInfo.FLAG_GUEST);
            assertThat(guest2).isNotNull();
            assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
                    guest2.getUserHandle())).isNotEqualTo(isFunDisallowed);
            removeUser(guest2.id, true);
            assertThat(mUserManager.getUserInfo(guest2.id)).isNull();
            assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
                    guest2.getUserHandle())).isFalse();
        } finally {
            mUserManager.setDefaultGuestRestrictions(origRestrictions);
        }
    }

    @MediumTest
    @Test
    public void testCacheInvalidatedAfterUserAddedOrRemoved() {
        final Bundle origRestrictions = mUserManager.getDefaultGuestRestrictions();
        try {
            final boolean isFunDisallowed = origRestrictions.getBoolean(UserManager.DISALLOW_FUN,
                    false);
            final UserInfo guest1 = mUserManager.createUser("Guest 1", UserInfo.FLAG_GUEST);
            assertThat(guest1).isNotNull();
            assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
                    guest1.getUserHandle())).isEqualTo(isFunDisallowed);
            removeUser(guest1.id, true);

            Bundle restrictions = new Bundle();
            restrictions.putBoolean(UserManager.DISALLOW_FUN, !isFunDisallowed);
            mUserManager.setDefaultGuestRestrictions(restrictions);
            int latest_id = guest1.id;
            // Cache removed id and few next ids.
            assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
                    UserHandle.of(latest_id))).isFalse();
            assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
                    UserHandle.of(latest_id + 1))).isFalse();
            assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
                    UserHandle.of(latest_id + 2))).isFalse();
            assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
                    UserHandle.of(latest_id + 3))).isFalse();

            UserInfo guest2 = mUserManager.createUser("Guest 2", UserInfo.FLAG_GUEST);
            assertThat(guest2).isNotNull();
            // Cache was invalidated after user was added
            assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
                    guest2.getUserHandle())).isTrue();
            removeUser(guest2.id, true);
            assertThat(mUserManager.getUserInfo(guest2.id)).isNull();
            // Cache was invalidated after user was removed
            assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
                    guest2.getUserHandle())).isFalse();
        } finally {
            mUserManager.setDefaultGuestRestrictions(origRestrictions);
        }
    }


    @MediumTest
    @Test
    public void testAddRemoveUsersAndRestrictions() {
        try {
            final UserInfo userInfo = mUserManager.createUser("Useroid",
                    UserManager.USER_TYPE_FULL_SECONDARY, 0);
            mUsersToRemove.add(userInfo.id);
            assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
                    userInfo.getUserHandle())).isFalse();
            mUserManager.setUserRestriction(UserManager.DISALLOW_FUN, true,
                    userInfo.getUserHandle());

            assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
                    userInfo.getUserHandle())).isTrue();
            removeUser(userInfo.id, true);
            assertThat(mUserManager.getUserSerialNumber(userInfo.id)).isEqualTo(-1);
            assertThat(mUserManager.getUserInfo(userInfo.id)).isNull();
            assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
                    userInfo.getUserHandle())).isFalse();
        } catch (java.lang.Exception e) {
        }
    }


    private void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    @MediumTest
    @Test
    public void testDefaultUserRestrictionsForPrivateProfile() {
        assumeTrue(mUserManager.canAddPrivateProfile());
        final int currentUserId = ActivityManager.getCurrentUser();
        UserInfo privateProfileInfo = null;
        try {
            privateProfileInfo = mUserManager.createProfileForUser(
                    "Private", UserManager.USER_TYPE_PROFILE_PRIVATE, 0, currentUserId, null);
            assertThat(privateProfileInfo).isNotNull();
        } catch (Exception e) {
            fail("Creation of private profile failed due to " + e.getMessage());
        }
        assertDefaultPrivateProfileRestrictions(privateProfileInfo.getUserHandle());
        // Assert cached values
        assertDefaultPrivateProfileRestrictions(privateProfileInfo.getUserHandle());
    }

    private void assertDefaultPrivateProfileRestrictions(UserHandle userHandle) {
        Bundle defaultPrivateProfileRestrictions =
                UserTypeFactory.getDefaultPrivateProfileRestrictions();
        for (String restriction : defaultPrivateProfileRestrictions.keySet()) {
            assertThat(mUserManager.hasUserRestrictionForUser(restriction, userHandle)).isTrue();
        }
    }

    private void assumeManagedUsersSupported() {
        // In Automotive, if headless system user is enabled, a managed user cannot be created
        // under a primary user.
@@ -270,9 +428,23 @@ public final class UserManagerCacheTest {
    }

    private void removeUser(int userId) {
        removeUser(userId, false);
    }

    private void removeUser(int userId, boolean waitForCompleteRemoval) {
        mUserManager.removeUser(userId);
        mUserRemovalWaiter.waitFor(userId);
        mUsersToRemove.remove(userId);
        if (waitForCompleteRemoval) {
            int serialNumber = mUserManager.getUserSerialNumber(userId);
            int timeout = REMOVE_USER_TIMEOUT_SECONDS * 5; // called every 200ms
            // Wait for the user to be removed from memory
            while (serialNumber > 0 && timeout > 0) {
                sleep(200);
                timeout--;
                serialNumber = mUserManager.getUserSerialNumber(userId);
            }
        }
    }

    private boolean isAutomotive() {