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

Commit c444d10c authored by Alex Johnston's avatar Alex Johnston Committed by Android (Google) Code Review
Browse files

Merge "Change the data structure of restrictions in UM" into rvc-dev

parents 5eed7459 d4c416fd
Loading
Loading
Loading
Loading
+256 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.pm;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.os.Bundle;
import android.os.UserManager;
import android.util.SparseArray;

import com.android.internal.annotations.VisibleForTesting;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

/**
 * Data structure that contains the mapping of users to user restrictions (either the user
 * restrictions that apply to them, or the user restrictions that they set, depending on the
 * circumstances).
 *
 * @hide
 */
public class RestrictionsSet {

    private static final String USER_ID = "user_id";
    private static final String TAG_RESTRICTIONS = "restrictions";
    private static final String TAG_RESTRICTIONS_USER = "restrictions_user";

    /**
     * Mapping of user restrictions.
     * Only non-empty restriction bundles are stored.
     * The key is the user id of the user.
     * userId -> restrictionBundle
     */
    private final SparseArray<Bundle> mUserRestrictions = new SparseArray<>(0);

    public RestrictionsSet() {
    }

    public RestrictionsSet(@UserIdInt int userId, @NonNull Bundle restrictions) {
        if (restrictions.isEmpty()) {
            throw new IllegalArgumentException("empty restriction bundle cannot be added.");
        }
        mUserRestrictions.put(userId, restrictions);
    }

    /**
     * Updates restriction bundle for a given user.
     * If new bundle is empty, record is removed from the array.
     *
     * @return whether restrictions bundle is different from the old one.
     */
    public boolean updateRestrictions(@UserIdInt int userId, @Nullable Bundle restrictions) {
        final boolean changed =
                !UserRestrictionsUtils.areEqual(mUserRestrictions.get(userId), restrictions);
        if (!changed) {
            return false;
        }
        if (!UserRestrictionsUtils.isEmpty(restrictions)) {
            mUserRestrictions.put(userId, restrictions);
        } else {
            mUserRestrictions.delete(userId);
        }
        return true;
    }

    /**
     * Moves a particular restriction from one restriction set to another, e.g. for all users.
     */
    public void moveRestriction(@NonNull RestrictionsSet destRestrictions, String restriction) {
        for (int i = 0; i < mUserRestrictions.size(); i++) {
            final int userId = mUserRestrictions.keyAt(i);
            final Bundle from = mUserRestrictions.valueAt(i);

            if (UserRestrictionsUtils.contains(from, restriction)) {
                from.remove(restriction);
                Bundle to = destRestrictions.getRestrictions(userId);
                if (to == null) {
                    to = new Bundle();
                    to.putBoolean(restriction, true);
                    destRestrictions.updateRestrictions(userId, to);
                } else {
                    to.putBoolean(restriction, true);
                }
                // Don't keep empty bundles.
                if (from.isEmpty()) {
                    mUserRestrictions.removeAt(i);
                    i--;
                }
            }
        }
    }

    /**
     * @return whether restrictions set has no restrictions.
     */
    public boolean isEmpty() {
        return mUserRestrictions.size() == 0;
    }

    /**
     * Merge all restrictions in restrictions set into one bundle. The original user restrictions
     * set does not get modified, instead a new bundle is returned.
     *
     * @return restrictions bundle containing all user restrictions.
     */
    public @NonNull Bundle mergeAll() {
        final Bundle result = new Bundle();
        for (int i = 0; i < mUserRestrictions.size(); i++) {
            UserRestrictionsUtils.merge(result, mUserRestrictions.valueAt(i));
        }
        return result;
    }

    /**
     * @return list of enforcing users that enforce a particular restriction.
     */
    public @NonNull List<UserManager.EnforcingUser> getEnforcingUsers(String restriction,
            @UserIdInt int deviceOwnerUserId) {
        final List<UserManager.EnforcingUser> result = new ArrayList<>();
        for (int i = 0; i < mUserRestrictions.size(); i++) {
            if (UserRestrictionsUtils.contains(mUserRestrictions.valueAt(i), restriction)) {
                result.add(getEnforcingUser(mUserRestrictions.keyAt(i), deviceOwnerUserId));
            }
        }
        return result;
    }

    private UserManager.EnforcingUser getEnforcingUser(@UserIdInt int userId,
            @UserIdInt int deviceOwnerUserId) {
        int source = deviceOwnerUserId == userId
                ? UserManager.RESTRICTION_SOURCE_DEVICE_OWNER
                : UserManager.RESTRICTION_SOURCE_PROFILE_OWNER;
        return new UserManager.EnforcingUser(userId, source);
    }

    /**
     * @return list of user restrictions for a given user. Null is returned if the user does not
     * have any restrictions.
     */
    public @Nullable Bundle getRestrictions(@UserIdInt int userId) {
        return mUserRestrictions.get(userId);
    }

    /**
     * Removes a given user from the restrictions set, returning true if the user has non-empty
     * restrictions before removal.
     */
    public boolean remove(@UserIdInt int userId) {
        boolean hasUserRestriction = mUserRestrictions.contains(userId);
        mUserRestrictions.remove(userId);
        return hasUserRestriction;
    }

    /**
     * Remove list of users and user restrictions.
     */
    public void removeAllRestrictions() {
        mUserRestrictions.clear();
    }

    /**
     * Serialize a given {@link RestrictionsSet} to XML.
     */
    public void writeRestrictions(@NonNull XmlSerializer serializer, @NonNull String outerTag)
            throws IOException {
        serializer.startTag(null, outerTag);
        for (int i = 0; i < mUserRestrictions.size(); i++) {
            serializer.startTag(null, TAG_RESTRICTIONS_USER);
            serializer.attribute(null, USER_ID, String.valueOf(mUserRestrictions.keyAt(i)));
            UserRestrictionsUtils.writeRestrictions(serializer, mUserRestrictions.valueAt(i),
                    TAG_RESTRICTIONS);
            serializer.endTag(null, TAG_RESTRICTIONS_USER);
        }
        serializer.endTag(null, outerTag);
    }

    /**
     * Read restrictions from XML.
     */
    public static RestrictionsSet readRestrictions(@NonNull XmlPullParser parser,
            @NonNull String outerTag) throws IOException, XmlPullParserException {
        RestrictionsSet restrictionsSet = new RestrictionsSet();
        int userId = 0;
        int type;

        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
            String tag = parser.getName();
            if (type == XmlPullParser.END_TAG && outerTag.equals(tag)) {
                return restrictionsSet;
            } else if (type == XmlPullParser.START_TAG && TAG_RESTRICTIONS_USER.equals(tag)) {
                userId = Integer.parseInt(parser.getAttributeValue(null, USER_ID));
            } else if (type == XmlPullParser.START_TAG && TAG_RESTRICTIONS.equals(tag)) {
                Bundle restrictions = UserRestrictionsUtils.readRestrictions(parser);
                restrictionsSet.updateRestrictions(userId, restrictions);
            }
        }
        throw new XmlPullParserException("restrictions cannot be read as xml is malformed.");
    }

    /**
     * Dumps {@link RestrictionsSet}.
     */
    public void dumpRestrictions(PrintWriter pw, String prefix) {
        boolean noneSet = true;
        for (int i = 0; i < mUserRestrictions.size(); i++) {
            pw.println(prefix + "User Id: " + mUserRestrictions.keyAt(i));
            UserRestrictionsUtils.dumpRestrictions(pw, prefix + "  ", mUserRestrictions.valueAt(i));
            noneSet = false;
        }
        if (noneSet) {
            pw.println(prefix + "none");
        }
    }

    public boolean containsKey(@UserIdInt int userId) {
        return mUserRestrictions.contains(userId);
    }

    @VisibleForTesting
    public int size() {
        return mUserRestrictions.size();
    }

    @VisibleForTesting
    public int keyAt(int index) {
        return mUserRestrictions.keyAt(index);
    }

    @VisibleForTesting
    public Bundle valueAt(int index) {
        return mUserRestrictions.valueAt(index);
    }

}
+187 −91

File changed.

Preview size limit exceeded, changes collapsed.

+8 −36
Original line number Diff line number Diff line
@@ -394,22 +394,6 @@ public class UserRestrictionsUtils {
        }
    }

    /**
     * Merges a sparse array of restrictions bundles into one.
     */
    @Nullable
    public static Bundle mergeAll(SparseArray<Bundle> restrictions) {
        if (restrictions.size() == 0) {
            return null;
        } else {
            final Bundle result = new Bundle();
            for (int i = 0; i < restrictions.size(); i++) {
                merge(result, restrictions.valueAt(i));
            }
            return result;
        }
    }

    /**
     * @return true if a restriction is settable by device owner.
     */
@@ -864,27 +848,15 @@ public class UserRestrictionsUtils {
    }

    /**
     * Moves a particular restriction from one array of bundles to another, e.g. for all users.
     * Moves a particular restriction from one array of restrictions sets to a restriction set,
     * e.g. for all users.
     */
    public static void moveRestriction(String restrictionKey, SparseArray<Bundle> srcRestrictions,
            SparseArray<Bundle> destRestrictions) {
        for (int i = 0; i < srcRestrictions.size(); i++) {
            final int key = srcRestrictions.keyAt(i);
            final Bundle from = srcRestrictions.valueAt(i);
            if (contains(from, restrictionKey)) {
                from.remove(restrictionKey);
                Bundle to = destRestrictions.get(key);
                if (to == null) {
                    to = new Bundle();
                    destRestrictions.append(key, to);
                }
                to.putBoolean(restrictionKey, true);
                // Don't keep empty bundles.
                if (from.isEmpty()) {
                    srcRestrictions.removeAt(i);
                    i--;
                }
            }
    public static void moveRestriction(String restrictionKey,
            SparseArray<RestrictionsSet> sourceRestrictionsSets,
            RestrictionsSet destRestrictionSet) {
        for (int i = 0; i < sourceRestrictionsSets.size(); i++) {
            final RestrictionsSet sourceRestrictionsSet = sourceRestrictionsSets.valueAt(i);
            sourceRestrictionsSet.moveRestriction(destRestrictionSet, restrictionKey);
        }
    }

+20 −1
Original line number Diff line number Diff line
@@ -25,6 +25,8 @@ import android.test.AndroidTestCase;
import android.util.Log;
import android.util.Printer;

import com.android.server.pm.RestrictionsSet;

import libcore.io.Streams;

import com.google.android.collect.Lists;
@@ -35,7 +37,6 @@ import org.junit.Assert;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
@@ -58,6 +59,13 @@ public class DpmTestUtils extends AndroidTestCase {
        return list == null ? 0 : list.size();
    }

    public static RestrictionsSet newRestrictions(int userId, String... restrictions) {
        Bundle localRestrictionsBundle = newRestrictions(restrictions);
        RestrictionsSet localRestrictions = new RestrictionsSet();
        localRestrictions.updateRestrictions(userId, localRestrictionsBundle);
        return localRestrictions;
    }

    public static Bundle newRestrictions(String... restrictions) {
        final Bundle ret = new Bundle();
        for (String restriction : restrictions) {
@@ -66,6 +74,17 @@ public class DpmTestUtils extends AndroidTestCase {
        return ret;
    }

    public static void assertRestrictions(RestrictionsSet expected, RestrictionsSet actual) {
        assertEquals(expected.size(), actual.size());

        for (int i = 0; i < expected.size(); i++) {
            int originatingUserId = expected.keyAt(i);
            Bundle actualRestrictions = actual.getRestrictions(originatingUserId);
            assertFalse(actualRestrictions.isEmpty());
            assertRestrictions(expected.getRestrictions(originatingUserId), actualRestrictions);
        }
    }

    public static void assertRestrictions(Bundle expected, Bundle actual) {
        final ArrayList<String> elist;
        if (expected == null) {
+191 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.pm;

import static com.android.server.devicepolicy.DpmTestUtils.assertRestrictions;
import static com.android.server.devicepolicy.DpmTestUtils.newRestrictions;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;

import androidx.test.runner.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.List;

/** Test for {@link RestrictionsSet}. */
@RunWith(AndroidJUnit4.class)
public class RestrictionsSetTest {

    private RestrictionsSet mRestrictionsSet = new RestrictionsSet();
    private final int originatingUserId = 0;

    @Test
    public void testUpdateRestrictions_addRestrictions() {
        Bundle restrictions = newRestrictions(UserManager.ENSURE_VERIFY_APPS);

        assertTrue(mRestrictionsSet.updateRestrictions(originatingUserId, restrictions));

        assertRestrictions(restrictions, mRestrictionsSet.getRestrictions(originatingUserId));
    }

    @Test
    public void testUpdateRestrictions_removeRestrictions() {
        Bundle restrictions = newRestrictions(UserManager.ENSURE_VERIFY_APPS);
        mRestrictionsSet.updateRestrictions(originatingUserId, restrictions);

        assertTrue(mRestrictionsSet.updateRestrictions(originatingUserId, new Bundle()));

        assertNull(mRestrictionsSet.getRestrictions(originatingUserId));
    }

    @Test
    public void testUpdateRestrictions_noChange() {
        Bundle restrictions = newRestrictions(UserManager.ENSURE_VERIFY_APPS);
        mRestrictionsSet.updateRestrictions(originatingUserId, restrictions);

        assertFalse(mRestrictionsSet.updateRestrictions(originatingUserId, restrictions));
    }

    @Test
    public void testMoveRestriction_containsRestriction() {
        RestrictionsSet destRestrictionsSet = new RestrictionsSet();

        String restriction = UserManager.DISALLOW_CONFIG_DATE_TIME;
        mRestrictionsSet.updateRestrictions(originatingUserId,
                newRestrictions(restriction));

        mRestrictionsSet.moveRestriction(destRestrictionsSet, restriction);

        assertNull(mRestrictionsSet.getRestrictions(originatingUserId));
        assertNotNull(destRestrictionsSet.getRestrictions(originatingUserId));
        assertRestrictions(newRestrictions(restriction),
                destRestrictionsSet.getRestrictions(originatingUserId));
    }

    @Test
    public void testMoveRestriction_doesNotContainRestriction() {
        RestrictionsSet destRestrictionsSet = new RestrictionsSet();

        mRestrictionsSet.updateRestrictions(originatingUserId,
                newRestrictions(UserManager.ENSURE_VERIFY_APPS));

        mRestrictionsSet.moveRestriction(destRestrictionsSet,
                UserManager.DISALLOW_CONFIG_DATE_TIME);

        assertRestrictions(newRestrictions(UserManager.ENSURE_VERIFY_APPS),
                mRestrictionsSet.getRestrictions(originatingUserId));
        assertNull(destRestrictionsSet.getRestrictions(originatingUserId));
    }

    @Test
    public void testIsEmpty_noRestrictions() {
        assertTrue(mRestrictionsSet.isEmpty());
    }

    @Test
    public void testIsEmpty_hasRestrictions() {
        mRestrictionsSet.updateRestrictions(originatingUserId,
                newRestrictions(UserManager.ENSURE_VERIFY_APPS,
                        UserManager.DISALLOW_CONFIG_DATE_TIME));

        assertFalse(mRestrictionsSet.isEmpty());
    }

    @Test
    public void testMergeAll_noRestrictions() {
        assertTrue(mRestrictionsSet.mergeAll().isEmpty());
    }

    @Test
    public void testMergeAll_hasRestrictions() {
        mRestrictionsSet.updateRestrictions(originatingUserId,
                newRestrictions(UserManager.ENSURE_VERIFY_APPS,
                        UserManager.DISALLOW_CONFIG_DATE_TIME));
        mRestrictionsSet.updateRestrictions(10,
                newRestrictions(UserManager.DISALLOW_ADD_USER,
                        UserManager.DISALLOW_AIRPLANE_MODE));

        Bundle actual = mRestrictionsSet.mergeAll();
        assertRestrictions(newRestrictions(UserManager.ENSURE_VERIFY_APPS,
                UserManager.DISALLOW_CONFIG_DATE_TIME, UserManager.DISALLOW_ADD_USER,
                UserManager.DISALLOW_AIRPLANE_MODE), actual);
    }

    @Test
    public void testGetEnforcingUsers_hasEnforcingUser() {
        mRestrictionsSet.updateRestrictions(originatingUserId,
                newRestrictions(UserManager.ENSURE_VERIFY_APPS));
        mRestrictionsSet.updateRestrictions(10,
                newRestrictions(UserManager.DISALLOW_ADD_USER));

        List<UserManager.EnforcingUser> enforcingUsers = mRestrictionsSet.getEnforcingUsers(
                UserManager.ENSURE_VERIFY_APPS, originatingUserId);

        UserManager.EnforcingUser enforcingUser1 = enforcingUsers.get(0);
        assertEquals(UserHandle.of(originatingUserId), enforcingUser1.getUserHandle());
        assertEquals(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER,
                enforcingUser1.getUserRestrictionSource());
    }

    @Test
    public void testGetEnforcingUsers_hasMultipleEnforcingUsers() {
        int originatingUserId2 = 10;
        mRestrictionsSet.updateRestrictions(originatingUserId,
                newRestrictions(UserManager.ENSURE_VERIFY_APPS));
        mRestrictionsSet.updateRestrictions(originatingUserId2,
                newRestrictions(UserManager.ENSURE_VERIFY_APPS));

        List<UserManager.EnforcingUser> enforcingUsers = mRestrictionsSet.getEnforcingUsers(
                UserManager.ENSURE_VERIFY_APPS, originatingUserId);

        assertEquals(2, enforcingUsers.size());
        for (UserManager.EnforcingUser enforcingUser : enforcingUsers) {
            int userId = enforcingUser.getUserHandle().getIdentifier();
            assertTrue((userId == originatingUserId) || (userId == originatingUserId2));
            if (userId == originatingUserId) {
                assertEquals(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER,
                        enforcingUser.getUserRestrictionSource());
            }
            if (userId == originatingUserId2) {
                assertEquals(UserManager.RESTRICTION_SOURCE_PROFILE_OWNER,
                        enforcingUser.getUserRestrictionSource());
            }
        }
    }

    @Test
    public void testGetEnforcingUsers_noEnforcingUsers() {
        mRestrictionsSet.updateRestrictions(originatingUserId,
                newRestrictions(UserManager.DISALLOW_USER_SWITCH));

        List<UserManager.EnforcingUser> enforcingUsers = mRestrictionsSet.getEnforcingUsers(
                UserManager.ENSURE_VERIFY_APPS, originatingUserId);

        assertTrue(enforcingUsers.isEmpty());
    }

}
Loading