Whitelist packages for user types

Creates a new SystemConfig xml entry which allows a device to whitelist
system packages to be installed on users when they are created, based on
the type of user.

System packages will be installed on users when they are created, or
during OTAs, based on this whitelist. The whitelist can be
enabled/disabled via a Config resource.

For any user type, system packages can be whitelisted or blacklisted.
If it is both (for the same user type), the blacklist takes priority.
If it is neither, it won't be installed (since it isn't whitelisted).

If a system package isn't mentioned in the whitelist file at all, for
any user, then its behaviour depends on the Config resource value, which
can optionally implicitly whitelist all such apps on all users.

For now, the list is mostly empty and the default config is set to be
enabled but implicitly whitelist all system packages that are not
mentioned.

Test: atest FrameworksServicesTests:SystemConfigTest
Test: atest com.android.server.pm.UserManagerServicePackageWhitelistTest
Test: manually test user 0 by flashall -w and checking packages
Test: manually test OTA by setting setprop persist.pm.mock-upgrade 1
Bug: 134605778

Change-Id: Ia098c1f597f66a1c946cfcc9b7771c25e8ceabf7
diff --git a/apct-tests/perftests/multiuser/AndroidManifest.xml b/apct-tests/perftests/multiuser/AndroidManifest.xml
index b2a9524..893c8ca 100644
--- a/apct-tests/perftests/multiuser/AndroidManifest.xml
+++ b/apct-tests/perftests/multiuser/AndroidManifest.xml
@@ -22,6 +22,7 @@
     <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 32107b4..e74e4a9 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -38,8 +38,10 @@
 import android.os.IBinder;
 import android.os.IProgressListener;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.perftests.utils.ShellHelper;
 import android.util.Log;
 import android.view.WindowManagerGlobal;
 
@@ -85,6 +87,14 @@
 
     private static final String DUMMY_PACKAGE_NAME = "perftests.multiuser.apps.dummyapp";
 
+    // Copy of UserSystemPackageInstaller whitelist mode constants.
+    private static final String PACKAGE_WHITELIST_MODE_PROP =
+            "persist.debug.user.package_whitelist_mode";
+    private static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE = 0;
+    private static final int USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE = 0b001;
+    private static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST = 0b100;
+    private static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT = -1;
+
     private UserManager mUm;
     private ActivityManager mAm;
     private IActivityManager mIam;
@@ -442,6 +452,55 @@
         }
     }
 
+    // TODO: This is just a POC. Do this properly and add more.
+    /** Tests starting (unlocking) a newly-created profile using the user-type-pkg-whitelist. */
+    @Test
+    public void managedProfileUnlock_usingWhitelist() throws Exception {
+        assumeTrue(mHasManagedUserFeature);
+        final int origMode = getUserTypePackageWhitelistMode();
+        setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE
+                | USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST);
+
+        try {
+            while (mRunner.keepRunning()) {
+                mRunner.pauseTiming();
+                final int userId = createManagedProfile();
+                mRunner.resumeTiming();
+
+                startUserInBackground(userId);
+
+                mRunner.pauseTiming();
+                removeUser(userId);
+                mRunner.resumeTiming();
+            }
+        } finally {
+            setUserTypePackageWhitelistMode(origMode);
+        }
+    }
+    /** Tests starting (unlocking) a newly-created profile NOT using the user-type-pkg-whitelist. */
+    @Test
+    public void managedProfileUnlock_notUsingWhitelist() throws Exception {
+        assumeTrue(mHasManagedUserFeature);
+        final int origMode = getUserTypePackageWhitelistMode();
+        setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE);
+
+        try {
+            while (mRunner.keepRunning()) {
+                mRunner.pauseTiming();
+                final int userId = createManagedProfile();
+                mRunner.resumeTiming();
+
+                startUserInBackground(userId);
+
+                mRunner.pauseTiming();
+                removeUser(userId);
+                mRunner.resumeTiming();
+            }
+        } finally {
+            setUserTypePackageWhitelistMode(origMode);
+        }
+    }
+
     /** Creates a new user, returning its userId. */
     private int createUserNoFlags() {
         return createUserWithFlags(/* flags= */ 0);
@@ -458,6 +517,10 @@
     private int createManagedProfile() {
         final UserInfo userInfo = mUm.createProfileForUser("TestProfile",
                 UserInfo.FLAG_MANAGED_PROFILE, mAm.getCurrentUser());
+        if (userInfo == null) {
+            throw new IllegalStateException("Creating managed profile failed. Most likely there is "
+                    + "already a pre-existing profile on the device.");
+        }
         mUsersToRemove.add(userInfo.id);
         return userInfo.id;
     }
@@ -627,6 +690,20 @@
         }
     }
 
+    /** Gets the PACKAGE_WHITELIST_MODE_PROP System Property. */
+    private int getUserTypePackageWhitelistMode() {
+        return SystemProperties.getInt(PACKAGE_WHITELIST_MODE_PROP,
+                USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT);
+    }
+
+    /** Sets the PACKAGE_WHITELIST_MODE_PROP System Property to the given value. */
+    private void setUserTypePackageWhitelistMode(int mode) {
+        String result = ShellHelper.runShellCommand(
+                String.format("setprop %s %d", PACKAGE_WHITELIST_MODE_PROP, mode));
+        attestFalse("Failed to set sysprop " + PACKAGE_WHITELIST_MODE_PROP + ": " + result,
+                result != null && result.contains("Failed"));
+    }
+
     private void removeUser(int userId) {
         try {
             mUm.removeUser(userId);
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 24ee213..a87e165 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -308,6 +308,17 @@
     public abstract String getNameForUid(int uid);
 
     /**
+     * Marks a package as installed (or not installed) for a given user.
+     *
+     * @param pkg the package whose installation is to be set
+     * @param userId the user for whom to set it
+     * @param installed the new installed state
+     * @return true if the installed state changed as a result
+     */
+    public abstract boolean setInstalled(PackageParser.Package pkg,
+            @UserIdInt int userId, boolean installed);
+
+    /**
      * Request to perform the second phase of ephemeral resolution.
      * @param responseObj The response of the first phase of ephemeral resolution
      * @param origIntent The original intent that triggered ephemeral resolution
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index e65d761..df652f1 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -126,6 +126,13 @@
     public static final int FLAG_SYSTEM = 0x00000800;
 
     /**
+     * Indicates that this user is some sort of profile. Right now, the only profile type is
+     * {@link #FLAG_MANAGED_PROFILE}, but this can include other types of profiles too if any
+     * are created in the future. This is therefore not a flag, but an OR of several flags.
+     */
+    public static final int PROFILE_FLAGS_MASK = FLAG_MANAGED_PROFILE;
+
+    /**
      * @hide
      */
     @IntDef(flag = true, prefix = "FLAG_", value = {
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 7cd3e95..697825d 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -35,6 +35,7 @@
 import android.util.SparseArray;
 import android.util.Xml;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.XmlUtils;
 
 import libcore.io.IoUtils;
@@ -50,6 +51,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Loads global system configuration info.
@@ -209,6 +211,10 @@
 
     private final ArraySet<String> mBugreportWhitelistedPackages = new ArraySet<>();
 
+    // Map of packagesNames to userTypes. Stored temporarily until cleared by UserManagerService().
+    private ArrayMap<String, Set<String>> mPackageToUserTypeWhitelist = new ArrayMap<>();
+    private ArrayMap<String, Set<String>> mPackageToUserTypeBlacklist = new ArrayMap<>();
+
     public static SystemConfig getInstance() {
         if (!isSystemProcess()) {
             Slog.wtf(TAG, "SystemConfig is being accessed by a process other than "
@@ -359,7 +365,48 @@
         return mBugreportWhitelistedPackages;
     }
 
+    /**
+     * Gets map of packagesNames to userTypes, dictating on which user types each package should be
+     * initially installed, and then removes this map from SystemConfig.
+     * Called by UserManagerService when it is constructed.
+     */
+    public ArrayMap<String, Set<String>> getAndClearPackageToUserTypeWhitelist() {
+        ArrayMap<String, Set<String>> r = mPackageToUserTypeWhitelist;
+        mPackageToUserTypeWhitelist = new ArrayMap<>(0);
+        return r;
+    }
+
+    /**
+     * Gets map of packagesNames to userTypes, dictating on which user types each package should NOT
+     * be initially installed, even if they are whitelisted, and then removes this map from
+     * SystemConfig.
+     * Called by UserManagerService when it is constructed.
+     */
+    public ArrayMap<String, Set<String>> getAndClearPackageToUserTypeBlacklist() {
+        ArrayMap<String, Set<String>> r = mPackageToUserTypeBlacklist;
+        mPackageToUserTypeBlacklist = new ArrayMap<>(0);
+        return r;
+    }
+
+    /**
+     * Only use for testing. Do NOT use in production code.
+     * @param readPermissions false to create an empty SystemConfig; true to read the permissions.
+     */
+    @VisibleForTesting
+    protected SystemConfig(boolean readPermissions) {
+        if (readPermissions) {
+            Slog.w(TAG, "Constructing a test SystemConfig");
+            readAllPermissions();
+        } else {
+            Slog.w(TAG, "Constructing an empty test SystemConfig");
+        }
+    }
+
     SystemConfig() {
+        readAllPermissions();
+    }
+
+    private void readAllPermissions() {
         // Read configuration from system
         readPermissions(Environment.buildPath(
                 Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL);
@@ -419,7 +466,8 @@
                 Environment.getSystemExtDirectory(), "etc", "permissions"), ALLOW_ALL);
     }
 
-    void readPermissions(File libraryDir, int permissionFlag) {
+    @VisibleForTesting
+    public void readPermissions(File libraryDir, int permissionFlag) {
         // Read permissions from given directory.
         if (!libraryDir.exists() || !libraryDir.isDirectory()) {
             if (permissionFlag == ALLOW_ALL) {
@@ -954,6 +1002,11 @@
                         }
                         XmlUtils.skipCurrentTag(parser);
                     } break;
+                    case "install-in-user-type": {
+                        // NB: We allow any directory permission to declare install-in-user-type.
+                        readInstallInUserType(parser,
+                                mPackageToUserTypeWhitelist, mPackageToUserTypeBlacklist);
+                    } break;
                     default: {
                         Slog.w(TAG, "Tag " + name + " is unknown in "
                                 + permFile + " at " + parser.getPositionDescription());
@@ -1091,6 +1144,53 @@
         }
     }
 
+    private void readInstallInUserType(XmlPullParser parser,
+            Map<String, Set<String>> doInstallMap,
+            Map<String, Set<String>> nonInstallMap)
+            throws IOException, XmlPullParserException {
+        final String packageName = parser.getAttributeValue(null, "package");
+        if (TextUtils.isEmpty(packageName)) {
+            Slog.w(TAG, "package is required for <install-in-user-type> in "
+                    + parser.getPositionDescription());
+            return;
+        }
+
+        Set<String> userTypesYes = doInstallMap.get(packageName);
+        Set<String> userTypesNo = nonInstallMap.get(packageName);
+        final int depth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, depth)) {
+            final String name = parser.getName();
+            if ("install-in".equals(name)) {
+                final String userType = parser.getAttributeValue(null, "user-type");
+                if (TextUtils.isEmpty(userType)) {
+                    Slog.w(TAG, "user-type is required for <install-in-user-type> in "
+                            + parser.getPositionDescription());
+                    continue;
+                }
+                if (userTypesYes == null) {
+                    userTypesYes = new ArraySet<>();
+                    doInstallMap.put(packageName, userTypesYes);
+                }
+                userTypesYes.add(userType);
+            } else if ("do-not-install-in".equals(name)) {
+                final String userType = parser.getAttributeValue(null, "user-type");
+                if (TextUtils.isEmpty(userType)) {
+                    Slog.w(TAG, "user-type is required for <install-in-user-type> in "
+                            + parser.getPositionDescription());
+                    continue;
+                }
+                if (userTypesNo == null) {
+                    userTypesNo = new ArraySet<>();
+                    nonInstallMap.put(packageName, userTypesNo);
+                }
+                userTypesNo.add(userType);
+            } else {
+                Slog.w(TAG, "unrecognized tag in <install-in-user-type> in "
+                        + parser.getPositionDescription());
+            }
+        }
+    }
+
     void readOemPermissions(XmlPullParser parser) throws IOException, XmlPullParserException {
         final String packageName = parser.getAttributeValue(null, "package");
         if (TextUtils.isEmpty(packageName)) {
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5fd53de..e3337b7 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2527,6 +2527,16 @@
          will be locked. -->
     <bool name="config_multiuserDelayUserDataLocking">false</bool>
 
+    <!-- Whether to only install system packages on a user if they're whitelisted for that user
+         type. These are flags and can be freely combined.
+         0 (0b000) - disable whitelist (install all system packages; no logging)
+         1 (0b001) - enforce (only install system packages if they are whitelisted)
+         2 (0b010) - log (log when a non-whitelisted package is run)
+         4 (0b100) - treat any package not mentioned in the whitelist file as implicitly whitelisted
+        Note: This list must be kept current with PACKAGE_WHITELIST_MODE_PROP in
+        frameworks/base/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java -->
+    <integer name="config_userTypePackageWhitelistMode">5</integer> <!-- 0b101 -->
+
     <!-- Whether UI for multi user should be shown -->
     <bool name="config_enableMultiUserUI">false</bool>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 761e02f..3fe907c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -511,6 +511,7 @@
   <java-symbol type="integer" name="config_multiuserMaximumUsers" />
   <java-symbol type="integer" name="config_multiuserMaxRunningUsers" />
   <java-symbol type="bool" name="config_multiuserDelayUserDataLocking" />
+  <java-symbol type="integer" name="config_userTypePackageWhitelistMode"/>
   <java-symbol type="integer" name="config_safe_media_volume_index" />
   <java-symbol type="integer" name="config_safe_media_volume_usb_mB" />
   <java-symbol type="integer" name="config_mobile_mtu" />
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 4493f3a..976974a 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -22,6 +22,12 @@
 }
 
 prebuilt_etc {
+    name: "preinstalled-packages-platform.xml",
+    sub_dir: "sysconfig",
+    src: "preinstalled-packages-platform.xml",
+}
+
+prebuilt_etc {
     name: "hiddenapi-package-whitelist.xml",
     sub_dir: "sysconfig",
     src: "hiddenapi-package-whitelist.xml",
diff --git a/data/etc/preinstalled-packages-platform.xml b/data/etc/preinstalled-packages-platform.xml
new file mode 100644
index 0000000..ccd8b5b
--- /dev/null
+++ b/data/etc/preinstalled-packages-platform.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+
+<!--
+This XML file declares which system packages should be initially installed for new users based on
+the type of user. All system packages on the device should ideally have an entry in an xml file
+(keys by its manifest name).
+
+Main user-types (every user will be at least one of these types) are:
+  SYSTEM    (user 0)
+  FULL      (any non-profile human user)
+  PROFILE   (profile human user)
+
+Additional optional types are:   GUEST,    RESTRICTED,    MANAGED_PROFILE,    EPHEMERAL,    DEMO
+
+The meaning of each of these user types is delineated by flags in
+frameworks/base/core/java/android/content/pm/UserInfo.java.
+See frameworks/base/services/core/java/com/android/server/pm/UserSystemPackageInstaller#getFlagsFromUserTypes
+
+The following three examples should cover most normal cases:
+
+1. For a system package to be pre-installed only in user 0:
+
+   <install-in-user-type package="com.android.example">
+       <install-in user-type="SYSTEM">
+   </install-in-user-type>
+
+
+2. For a system package to be pre-installed on all human users (e.g. a web browser), i.e. to be
+installed on any user of type type FULL or PROFILE (since this covers all human users):
+
+   <install-in-user-type package="com.android.example">
+       <install-in user-type="FULL">
+       <install-in user-type="PROFILE">
+   </install-in-user-type>
+
+
+3. For a system package to be pre-installed on all human users except for profile users (e.g. a
+wallpaper app, since profiles cannot display wallpaper):
+
+   <install-in-user-type package="com.android.example">
+       <install-in user-type="FULL">
+   </install-in-user-type>
+
+
+Some system packages truly are required to be on all users, regardless of type, in which case use:
+   <install-in-user-type package="com.android.example">
+       <install-in user-type="SYSTEM">
+       <install-in user-type="FULL">
+       <install-in user-type="PROFILE">
+   </install-in-user-type>
+
+More fine-grained options are also available (see below). Additionally, packages can blacklist
+user types. Blacklists override any whitelisting (in any file).
+E.g.
+     <install-in-user-type package="com.android.example">
+        <install-in user-type="FULL" />
+        <do-not-install-in user-type="GUEST" />
+    </install-in-user-type>
+
+If a user is of type FULL and GUEST, this package will NOT be installed, because the
+'do-not-install-in' takes precedence over 'install-in'.
+
+The way that a device treats system packages that do not have any entry (for any user type) at all
+is determined by the config resource value config_userTypePackageWhitelistMode.
+See frameworks/base/core/res/res/values/config.xml#config_userTypePackageWhitelistMode.
+
+Changes to the whitelist during system updates can result in installing new system packages
+to pre-existing users, but cannot uninstall system packages from pre-existing users.
+-->
+<config>
+    <install-in-user-type package="com.android.providers.settings">
+        <install-in user-type="SYSTEM" />
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+</config>
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2a85c89..949478d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -137,7 +137,6 @@
 import android.content.IntentSender.SendIntentException;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.AppsQueryHelper;
 import android.content.pm.AuxiliaryResolveInfo;
 import android.content.pm.ChangedPackages;
 import android.content.pm.ComponentInfo;
@@ -2473,54 +2472,21 @@
         PackageManagerService m = new PackageManagerService(injector, factoryTest, onlyCore);
         t.traceEnd(); // "create package manager"
 
-        m.enableSystemUserPackages();
+        m.installWhitelistedSystemPackages();
         ServiceManager.addService("package", m);
         final PackageManagerNative pmn = m.new PackageManagerNative();
         ServiceManager.addService("package_native", pmn);
         return m;
     }
 
-    private void enableSystemUserPackages() {
-        if (!UserManager.isSplitSystemUser()) {
-            return;
-        }
-        // For system user, enable apps based on the following conditions:
-        // - app is whitelisted or belong to one of these groups:
-        //   -- system app which has no launcher icons
-        //   -- system app which has INTERACT_ACROSS_USERS permission
-        //   -- system IME app
-        // - app is not in the blacklist
-        AppsQueryHelper queryHelper = new AppsQueryHelper(this);
-        Set<String> enableApps = new ArraySet<>();
-        enableApps.addAll(queryHelper.queryApps(AppsQueryHelper.GET_NON_LAUNCHABLE_APPS
-                | AppsQueryHelper.GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM
-                | AppsQueryHelper.GET_IMES, /* systemAppsOnly */ true, UserHandle.SYSTEM));
-        ArraySet<String> wlApps = SystemConfig.getInstance().getSystemUserWhitelistedApps();
-        enableApps.addAll(wlApps);
-        enableApps.addAll(queryHelper.queryApps(AppsQueryHelper.GET_REQUIRED_FOR_SYSTEM_USER,
-                /* systemAppsOnly */ false, UserHandle.SYSTEM));
-        ArraySet<String> blApps = SystemConfig.getInstance().getSystemUserBlacklistedApps();
-        enableApps.removeAll(blApps);
-        Log.i(TAG, "Applications installed for system user: " + enableApps);
-        List<String> allAps = queryHelper.queryApps(0, /* systemAppsOnly */ false,
-                UserHandle.SYSTEM);
-        final int allAppsSize = allAps.size();
+    /** Install/uninstall system packages for all users based on their user-type, as applicable. */
+    private void installWhitelistedSystemPackages() {
         synchronized (mLock) {
-            for (int i = 0; i < allAppsSize; i++) {
-                String pName = allAps.get(i);
-                PackageSetting pkgSetting = mSettings.mPackages.get(pName);
-                // Should not happen, but we shouldn't be failing if it does
-                if (pkgSetting == null) {
-                    continue;
-                }
-                boolean install = enableApps.contains(pName);
-                if (pkgSetting.getInstalled(UserHandle.USER_SYSTEM) != install) {
-                    Log.i(TAG, (install ? "Installing " : "Uninstalling ") + pName
-                            + " for system user");
-                    pkgSetting.setInstalled(install, UserHandle.USER_SYSTEM);
-                }
+            final boolean scheduleWrite = mUserManager.installWhitelistedSystemPackages(
+                    isFirstBoot(), isDeviceUpgrading());
+            if (scheduleWrite) {
+                scheduleWritePackageRestrictionsLocked(UserHandle.USER_ALL);
             }
-            scheduleWritePackageRestrictionsLocked(UserHandle.USER_SYSTEM);
         }
     }
 
@@ -22307,10 +22273,19 @@
         }
     }
 
-    /** Called by UserManagerService */
-    void createNewUser(int userId, String[] disallowedPackages) {
+    /**
+     * Called by UserManagerService.
+     *
+     * @param installablePackages system packages that should be initially installed for this user,
+     *                            or {@code null} if all system packages should be installed
+     * @param disallowedPackages packages that should not be initially installed. Takes precedence
+     *                           over installablePackages.
+     */
+    void createNewUser(int userId, @Nullable Set<String> installablePackages,
+            String[] disallowedPackages) {
         synchronized (mInstallLock) {
-            mSettings.createNewUserLI(this, mInstaller, userId, disallowedPackages);
+            mSettings.createNewUserLI(this, mInstaller, userId,
+                    installablePackages, disallowedPackages);
         }
         synchronized (mLock) {
             scheduleWritePackageRestrictionsLocked(userId);
@@ -23119,6 +23094,19 @@
         }
 
         @Override
+        public boolean setInstalled(PackageParser.Package pkg, @UserIdInt int userId,
+                boolean installed) {
+            synchronized (mLock) {
+                final PackageSetting ps = mSettings.mPackages.get(pkg.packageName);
+                if (ps.getInstalled(userId) != installed) {
+                    ps.setInstalled(installed, userId);
+                    return true;
+                }
+                return false;
+            }
+        }
+
+        @Override
         public void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj,
                 Intent origIntent, String resolvedType, String callingPackage,
                 Bundle verificationBundle, int userId) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 1873a4e..4349ea7 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4014,8 +4014,9 @@
         }
     }
 
-    void createNewUserLI(@NonNull PackageManagerService service,
-            @NonNull Installer installer, int userHandle, String[] disallowedPackages) {
+    void createNewUserLI(@NonNull PackageManagerService service, @NonNull Installer installer,
+            @UserIdInt int userHandle, @Nullable Set<String> installablePackages,
+            String[] disallowedPackages) {
         final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
                 Trace.TRACE_TAG_PACKAGE_MANAGER);
         t.traceBegin("createNewUser-" + userHandle);
@@ -4025,6 +4026,7 @@
         String[] seinfos;
         int[] targetSdkVersions;
         int packagesCount;
+        final boolean skipPackageWhitelist = installablePackages == null;
         synchronized (mLock) {
             Collection<PackageSetting> packages = mPackages.values();
             packagesCount = packages.size();
@@ -4040,6 +4042,7 @@
                     continue;
                 }
                 final boolean shouldInstall = ps.isSystem() &&
+                        (skipPackageWhitelist || installablePackages.contains(ps.name)) &&
                         !ArrayUtils.contains(disallowedPackages, ps.name) &&
                         !ps.pkg.applicationInfo.hiddenUntilInstalled;
                 // Only system apps are initially installed.
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 9371c44..5f86708 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -250,6 +250,9 @@
 
     private static final IBinder mUserRestriconToken = new Binder();
 
+    /** Installs system packages based on user-type. */
+    private final UserSystemPackageInstaller mSystemPackageInstaller;
+
     /**
      * Internal non-parcelable wrapper for UserInfo that is not exposed to other system apps.
      */
@@ -550,6 +553,7 @@
             readUserListLP();
             sInstance = this;
         }
+        mSystemPackageInstaller = new UserSystemPackageInstaller(this);
         mLocalService = new LocalService();
         LocalServices.addService(UserManagerInternal.class, mLocalService);
         mLockPatternUtils = new LockPatternUtils(mContext);
@@ -2842,8 +2846,10 @@
                     StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
             t.traceEnd();
 
+            final Set<String> installablePackages =
+                    mSystemPackageInstaller.getInstallablePackagesForUserType(flags);
             t.traceBegin("PM.createNewUser");
-            mPm.createNewUser(userId, disallowedPackages);
+            mPm.createNewUser(userId, installablePackages, disallowedPackages);
             t.traceEnd();
 
             userInfo.partial = false;
@@ -2877,6 +2883,11 @@
         return userInfo;
     }
 
+    /** Install/uninstall system packages for all users based on their user-type, as applicable. */
+    boolean installWhitelistedSystemPackages(boolean isFirstBoot, boolean isUpgrade) {
+        return mSystemPackageInstaller.installWhitelistedSystemPackages(isFirstBoot, isUpgrade);
+    }
+
     @VisibleForTesting
     UserData putUserInfo(UserInfo userInfo) {
         final UserData userData = new UserData();
@@ -3863,6 +3874,10 @@
         pw.println("  Is split-system user: " + UserManager.isSplitSystemUser());
         pw.println("  Is headless-system mode: " + UserManager.isHeadlessSystemUserMode());
         pw.println("  User version: " + mUserVersion);
+
+        // Dump package whitelist
+        pw.println();
+        mSystemPackageInstaller.dump(pw);
     }
 
     private static void dumpTimeAgo(PrintWriter pw, StringBuilder sb, long nowTime, long time) {
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
new file mode 100644
index 0000000..036d1e8
--- /dev/null
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -0,0 +1,459 @@
+/*
+ * Copyright (C) 2019 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.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageParser;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
+
+/**
+ * Responsible for un/installing system packages based on user type.
+ *
+ * <p>Uses the SystemConfig's install-in-user-type whitelist;
+ * see {@link SystemConfig#getAndClearPackageToUserTypeWhitelist} and
+ * {@link SystemConfig#getAndClearPackageToUserTypeBlacklist}.
+ *
+ * <p>If {@link #isEnforceMode()} is false, then all system packages are always installed for all
+ * users. The following applies when it is true.
+ *
+ * Any package can be in one of three states in the SystemConfig whitelist
+ * <ol>
+ *     <li>Explicitly blacklisted for a particular user type</li>
+ *     <li>Explicitly whitelisted for a particular user type</li>
+ *     <li>Not mentioned at all, for any user type (neither whitelisted nor blacklisted)</li>
+ * </ol>
+ * Blacklisting always takes precedence - if a package is blacklisted for a particular user,
+ * it won't be installed on that type of user (even if it is also whitelisted for that user).
+ * Next comes whitelisting - if it is whitelisted for a particular user, it will be installed on
+ * that type of user (as long as it isn't blacklisted).
+ * Finally, if the package is not mentioned at all (i.e. neither whitelisted nor blacklisted for
+ * any user types) in the SystemConfig 'install-in-user-type' lists
+ * then:
+ * <ul>
+ *     <li>If {@link #isImplicitWhitelistMode()}, the package is implicitly treated as whitelisted
+ *          for all users</li>
+ *     <li>Otherwise, the package is implicitly treated as blacklisted for all non-SYSTEM users</li>
+ *     <li>Either way, for {@link UserHandle#USER_SYSTEM}, the package will be implicitly
+ *          whitelisted so that it can be used for local development purposes.</li>
+ * </ul>
+ */
+class UserSystemPackageInstaller {
+    private static final String TAG = "UserManagerService";
+
+    /**
+     * System Property whether to only install system packages on a user if they're whitelisted for
+     * that user type. These are flags and can be freely combined.
+     * <ul>
+     * <li> 0 (0b000) - disable whitelist (install all system packages; no logging)</li>
+     * <li> 1 (0b001) - enforce (only install system packages if they are whitelisted)</li>
+     * <li> 2 (0b010) - log (log when a non-whitelisted package is run)</li>
+     * <li> 4 (0b100) - implicitly whitelist any package not mentioned in the whitelist</li>
+     * <li>-1         - use device default (as defined in res/res/values/config.xml)</li>
+     * </ul>
+     * Note: This list must be kept current with config_userTypePackageWhitelistMode in
+     * frameworks/base/core/res/res/values/config.xml
+     */
+    static final String PACKAGE_WHITELIST_MODE_PROP = "persist.debug.user.package_whitelist_mode";
+    static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE = 0;
+    static final int USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE = 0b001;
+    static final int USER_TYPE_PACKAGE_WHITELIST_MODE_LOG = 0b010;
+    static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST = 0b100;
+    static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT = -1;
+
+    @IntDef(flag = true, prefix = "USER_TYPE_PACKAGE_WHITELIST_MODE_", value = {
+            USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE,
+            USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE,
+            USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE,
+            USER_TYPE_PACKAGE_WHITELIST_MODE_LOG,
+            USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PackageWhitelistMode {}
+
+    /**
+     * Maps system package manifest names to the user flags on which they should be initially
+     * installed.
+     * <p>Packages that are whitelisted, but then blacklisted so that they aren't to be installed on
+     * any user, are purposefully still present in this list.
+     */
+    private final ArrayMap<String, Integer> mWhitelitsedPackagesForUserTypes;
+
+    private final UserManagerService mUm;
+
+    UserSystemPackageInstaller(UserManagerService ums) {
+        mUm = ums;
+        mWhitelitsedPackagesForUserTypes =
+                determineWhitelistedPackagesForUserTypes(SystemConfig.getInstance());
+    }
+
+    /** Constructor for testing purposes. */
+    @VisibleForTesting
+    UserSystemPackageInstaller(UserManagerService ums, ArrayMap<String, Integer> whitelist) {
+        mUm = ums;
+        mWhitelitsedPackagesForUserTypes = whitelist;
+    }
+
+    /**
+     * During OTAs and first boot, install/uninstall all system packages for all users based on the
+     * user's UserInfo flags and the SystemConfig whitelist.
+     * We do NOT uninstall packages during an OTA though.
+     *
+     * This is responsible for enforcing the whitelist for pre-existing users (i.e. USER_SYSTEM);
+     * enforcement for new users is done when they are created in UserManagerService.createUser().
+     */
+    boolean installWhitelistedSystemPackages(boolean isFirstBoot, boolean isUpgrade) {
+        final int mode = getWhitelistMode();
+        checkWhitelistedSystemPackages(mode);
+        if (!isUpgrade && !isFirstBoot) {
+            return false;
+        }
+        Slog.i(TAG, "Reviewing whitelisted packages due to "
+                + (isFirstBoot ? "[firstBoot]" : "") + (isUpgrade ? "[upgrade]" : ""));
+        final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+        // Install/uninstall system packages per user.
+        for (int userId : mUm.getUserIds()) {
+            final Set<String> userWhitelist = getInstallablePackagesForUserId(userId);
+            pmInt.forEachPackage(pkg -> {
+                if (!pkg.isSystem()) {
+                    return;
+                }
+                final boolean install =
+                        (userWhitelist == null || userWhitelist.contains(pkg.packageName))
+                        && !pkg.applicationInfo.hiddenUntilInstalled;
+                if (isUpgrade && !isFirstBoot && !install) {
+                    return; // To be careful, we don’t uninstall apps during OTAs
+                }
+                final boolean changed = pmInt.setInstalled(pkg, userId, install);
+                if (changed) {
+                    Slog.i(TAG, (install ? "Installed " : "Uninstalled ")
+                            + pkg.packageName + " for user " + userId);
+                }
+            });
+        }
+        return true;
+    }
+
+    /**
+     * Checks whether the system packages and the mWhitelistedPackagesForUserTypes whitelist are
+     * in 1-to-1 correspondence.
+     */
+    private void checkWhitelistedSystemPackages(@PackageWhitelistMode int mode) {
+        if (!isLogMode(mode) && !isEnforceMode(mode)) {
+            return;
+        }
+        Slog.v(TAG,  "Checking that all system packages are whitelisted.");
+        final Set<String> allWhitelistedPackages = getWhitelistedSystemPackages();
+        PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+
+        // Check whether all whitelisted packages are indeed on the system.
+        for (String pkgName : allWhitelistedPackages) {
+            PackageParser.Package pkg = pmInt.getPackage(pkgName);
+            if (pkg == null) {
+                Slog.w(TAG, pkgName + " is whitelisted but not present.");
+            } else if (!pkg.isSystem()) {
+                Slog.w(TAG, pkgName + " is whitelisted and present but not a system package.");
+            }
+        }
+
+        // Check whether all system packages are indeed whitelisted.
+        if (isImplicitWhitelistMode(mode) && !isLogMode(mode)) {
+            return;
+        }
+        final boolean doWtf = isEnforceMode(mode);
+        pmInt.forEachPackage(pkg -> {
+            if (pkg.isSystem() && !allWhitelistedPackages.contains(pkg.manifestPackageName)) {
+                final String msg = "System package " + pkg.manifestPackageName
+                        + " is not whitelisted using 'install-in-user-type' in SystemConfig "
+                        + "for any user types!";
+                if (doWtf) {
+                    Slog.wtf(TAG, msg);
+                } else {
+                    Slog.e(TAG, msg);
+                }
+            }
+        });
+    }
+
+    /** Whether to only install system packages in new users for which they are whitelisted. */
+    boolean isEnforceMode() {
+        return isEnforceMode(getWhitelistMode());
+    }
+
+    /**
+     * Whether to log a warning concerning potential problems with the user-type package whitelist.
+     */
+    boolean isLogMode() {
+        return isLogMode(getWhitelistMode());
+    }
+
+    /**
+     * Whether to treat all packages that are not mentioned at all in the whitelist to be implicitly
+     * whitelisted for all users.
+     */
+    boolean isImplicitWhitelistMode() {
+        return isImplicitWhitelistMode(getWhitelistMode());
+    }
+
+    /** See {@link #isEnforceMode()}. */
+    private static boolean isEnforceMode(int whitelistMode) {
+        return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE) != 0;
+    }
+
+    /** See {@link #isLogMode()}. */
+    private static boolean isLogMode(int whitelistMode) {
+        return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_LOG) != 0;
+    }
+
+    /** See {@link #isImplicitWhitelistMode()}. */
+    private static boolean isImplicitWhitelistMode(int whitelistMode) {
+        return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST) != 0;
+    }
+
+    /** Gets the PackageWhitelistMode for use of {@link #mWhitelitsedPackagesForUserTypes}. */
+    private @PackageWhitelistMode int getWhitelistMode() {
+        final int runtimeMode = SystemProperties.getInt(
+                PACKAGE_WHITELIST_MODE_PROP, USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT);
+        if (runtimeMode != USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT) {
+            return runtimeMode;
+        }
+        return Resources.getSystem()
+                .getInteger(com.android.internal.R.integer.config_userTypePackageWhitelistMode);
+    }
+
+    /**
+     * Gets the system packages names that should be installed on the given user.
+     * See {@link #getInstallablePackagesForUserType(int)}.
+     */
+    private @Nullable Set<String> getInstallablePackagesForUserId(@UserIdInt int userId) {
+        return getInstallablePackagesForUserType(mUm.getUserInfo(userId).flags);
+    }
+
+    /**
+     * Gets the system package names that should be installed on a user with the given flags, as
+     * determined by SystemConfig, the whitelist mode, and the apps actually on the device.
+     * Names are the {@link PackageParser.Package#packageName}, not necessarily the manifest names.
+     *
+     * Returns null if all system packages should be installed (due enforce-mode being off).
+     */
+    @Nullable Set<String> getInstallablePackagesForUserType(int flags) {
+        final int mode = getWhitelistMode();
+        if (!isEnforceMode(mode)) {
+            return null;
+        }
+        final boolean isSystemUser = (flags & UserInfo.FLAG_SYSTEM) != 0;
+        final boolean isImplicitWhitelistMode = isImplicitWhitelistMode(mode);
+        final Set<String> whitelistedPackages = getWhitelistedPackagesForUserType(flags);
+
+        final Set<String> installPackages = new ArraySet<>();
+        final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+        pmInt.forEachPackage(pkg -> {
+            if (!pkg.isSystem()) {
+                return;
+            }
+            if (shouldInstallPackage(pkg, mWhitelitsedPackagesForUserTypes,
+                    whitelistedPackages, isImplicitWhitelistMode, isSystemUser)) {
+                // Although the whitelist uses manifest names, this function returns packageNames.
+                installPackages.add(pkg.packageName);
+            }
+        });
+        return installPackages;
+    }
+
+    /**
+     * Returns whether the given system package should be installed on the given user, based on the
+     * the given whitelist of system packages.
+     *
+     * @param sysPkg the system package. Must be a system package; no verification for this is done.
+     * @param userTypeWhitelist map of package manifest names to user flags on which they should be
+     *                          installed
+     * @param userWhitelist set of package manifest names that should be installed on this
+     *                      particular user. This must be consistent with userTypeWhitelist, but is
+     *                      passed in separately to avoid repeatedly calculating it from
+     *                      userTypeWhitelist.
+     * @param isImplicitWhitelistMode whether non-mentioned packages are implicitly whitelisted.
+     * @param isSystemUser whether the user is USER_SYSTEM (which gets special treatment).
+     */
+    @VisibleForTesting
+    static boolean shouldInstallPackage(PackageParser.Package sysPkg,
+            @NonNull ArrayMap<String, Integer> userTypeWhitelist,
+            @NonNull Set<String> userWhitelist, boolean isImplicitWhitelistMode,
+            boolean isSystemUser) {
+
+        final String pkgName = sysPkg.manifestPackageName;
+        boolean install = (isImplicitWhitelistMode && !userTypeWhitelist.containsKey(pkgName))
+                || userWhitelist.contains(pkgName);
+
+        // For the purposes of local development, any package that isn't even mentioned in the
+        // whitelist at all is implicitly treated as whitelisted for the SYSTEM user.
+        if (!install && isSystemUser && !userTypeWhitelist.containsKey(pkgName)) {
+            install = true;
+            Slog.e(TAG, "System package " + pkgName + " is not mentioned "
+                    + "in SystemConfig's 'install-in-user-type' but we are "
+                    + "implicitly treating it as whitelisted for the SYSTEM user.");
+        }
+        return install;
+    }
+
+    /**
+     * Gets the package manifest names that are whitelisted for a user with the given flags,
+     * as determined by SystemConfig.
+     */
+    @VisibleForTesting
+    @NonNull Set<String> getWhitelistedPackagesForUserType(int flags) {
+        Set<String> installablePkgs = new ArraySet<>(mWhitelitsedPackagesForUserTypes.size());
+        for (int i = 0; i < mWhitelitsedPackagesForUserTypes.size(); i++) {
+            String pkgName = mWhitelitsedPackagesForUserTypes.keyAt(i);
+            int whitelistedUserTypes = mWhitelitsedPackagesForUserTypes.valueAt(i);
+            if ((flags & whitelistedUserTypes) != 0) {
+                installablePkgs.add(pkgName);
+            }
+        }
+        return installablePkgs;
+    }
+
+    /**
+     * Set of package manifest names that are included anywhere in the package-to-user-type
+     * whitelist, as determined by SystemConfig.
+     *
+     * Packages that are whitelisted, but then blacklisted so that they aren't to be installed on
+     * any user, are still present in this list, since that is a valid scenario (e.g. if an OEM
+     * completely blacklists an AOSP app).
+     */
+    private Set<String> getWhitelistedSystemPackages() {
+        return mWhitelitsedPackagesForUserTypes.keySet();
+    }
+
+    /**
+     * Returns a map of package manifest names to the user flags on which it is to be installed.
+     * Also, clears this data from SystemConfig where it was stored inefficiently (and therefore
+     * should be called exactly once, even if the data isn't useful).
+     *
+     * Any system packages not present in this map should not even be on the device at all.
+     * To enforce this:
+     * <ul>
+     *  <li>Illegal user types are ignored.</li>
+     *  <li>Packages that never whitelisted at all (even if they are explicitly blacklisted) are
+     *          ignored.</li>
+     *  <li>Packages that are blacklisted whenever they are whitelisted will be stored with the
+     *          flag 0 (since this is a valid scenario, e.g. if an OEM completely blacklists an AOSP
+     *          app).</li>
+     * </ul>
+     */
+    @VisibleForTesting
+    static ArrayMap<String, Integer> determineWhitelistedPackagesForUserTypes(
+            SystemConfig sysConfig) {
+
+        final ArrayMap<String, Set<String>> whitelist =
+                sysConfig.getAndClearPackageToUserTypeWhitelist();
+        // result maps packageName -> userTypes on which the package should be installed.
+        final ArrayMap<String, Integer> result = new ArrayMap<>(whitelist.size() + 1);
+        // First, do the whitelisted user types.
+        for (int i = 0; i < whitelist.size(); i++) {
+            final String pkgName = whitelist.keyAt(i);
+            final int flags = getFlagsFromUserTypes(whitelist.valueAt(i));
+            if (flags != 0) {
+                result.put(pkgName, flags);
+            }
+        }
+        // Then, un-whitelist any blacklisted user types.
+        // TODO(b/141370854): Right now, the blacklist is actually just an 'unwhitelist'. Which
+        //                    direction we go depends on how we design user subtypes, which is still
+        //                    being designed. For now, unwhitelisting works for current use-cases.
+        final ArrayMap<String, Set<String>> blacklist =
+                sysConfig.getAndClearPackageToUserTypeBlacklist();
+        for (int i = 0; i < blacklist.size(); i++) {
+            final String pkgName = blacklist.keyAt(i);
+            final int nonFlags = getFlagsFromUserTypes(blacklist.valueAt(i));
+            final Integer flags = result.get(pkgName);
+            if (flags != null) {
+                result.put(pkgName, flags & ~nonFlags);
+            }
+        }
+        // Regardless of the whitelists/blacklists, ensure mandatory packages.
+        result.put("android",
+                UserInfo.FLAG_SYSTEM | UserInfo.FLAG_FULL | UserInfo.PROFILE_FLAGS_MASK);
+        return result;
+    }
+
+    /** Converts a user types, as used in SystemConfig, to a UserInfo flag. */
+    private static int getFlagsFromUserTypes(Iterable<String> userTypes) {
+        int flags = 0;
+        for (String type : userTypes) {
+            switch (type) {
+                case "GUEST":
+                    flags |= UserInfo.FLAG_GUEST;
+                    break;
+                case "RESTRICTED":
+                    flags |= UserInfo.FLAG_RESTRICTED;
+                    break;
+                case "MANAGED_PROFILE":
+                    flags |= UserInfo.FLAG_MANAGED_PROFILE;
+                    break;
+                case "EPHEMERAL":
+                    flags |= UserInfo.FLAG_EPHEMERAL;
+                    break;
+                case "DEMO":
+                    flags |= UserInfo.FLAG_DEMO;
+                    break;
+                case "FULL":
+                    flags |= UserInfo.FLAG_FULL;
+                    break;
+                case "SYSTEM":
+                    flags |= UserInfo.FLAG_SYSTEM;
+                    break;
+                case "PROFILE":
+                    flags |= UserInfo.PROFILE_FLAGS_MASK;
+                    break;
+                default:
+                    Slog.w(TAG, "SystemConfig contained an invalid user type: " + type);
+                    break;
+                // Other UserInfo flags are forbidden.
+                // In particular, FLAG_INITIALIZED, FLAG_DISABLED, FLAG_QUIET_MODE are inapplicable.
+                // The following are invalid now, but are reconsiderable: FLAG_PRIMARY, FLAG_ADMIN.
+            }
+        }
+        return flags;
+    }
+
+    void dump(PrintWriter pw) {
+        for (int i = 0; i < mWhitelitsedPackagesForUserTypes.size(); i++) {
+            final String pkgName = mWhitelitsedPackagesForUserTypes.keyAt(i);
+            final String whitelistedUserTypes =
+                    UserInfo.flagsToString(mWhitelitsedPackagesForUserTypes.valueAt(i));
+            pw.println("    " + pkgName + ": " + whitelistedUserTypes);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/SystemConfigTest.java
new file mode 100644
index 0000000..ff03391
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/SystemConfigTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2019 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;
+
+import static org.junit.Assert.assertEquals;
+
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.Set;
+
+/**
+ * Tests for {@link SystemConfig}.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:SystemConfigTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SystemConfigTest {
+    private static final String LOG_TAG = "SystemConfigTest";
+
+    private SystemConfig mSysConfig;
+
+    @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+    @Before
+    public void setUp() throws Exception {
+        mSysConfig = new SystemConfigTestClass();
+    }
+
+    /**
+     * Subclass of SystemConfig without running the constructor.
+     */
+    private class SystemConfigTestClass extends SystemConfig {
+        SystemConfigTestClass() {
+            super(false);
+        }
+    }
+
+    /**
+     * Tests that readPermissions works correctly for the tag: install-in-user-type
+     */
+    @Test
+    public void testInstallInUserType() throws Exception {
+        final String contents1 =
+                  "<permissions>\n"
+                + "    <install-in-user-type package=\"com.android.package1\">\n"
+                + "        <install-in user-type=\"FULL\" />\n"
+                + "        <install-in user-type=\"PROFILE\" />\n"
+                + "    </install-in-user-type>\n"
+                + "    <install-in-user-type package=\"com.android.package2\">\n"
+                + "        <install-in user-type=\"FULL\" />\n"
+                + "        <install-in user-type=\"PROFILE\" />\n"
+                + "        <do-not-install-in user-type=\"GUEST\" />\n"
+                + "    </install-in-user-type>\n"
+                + "</permissions>";
+
+        final String contents2 =
+                  "<permissions>\n"
+                + "    <install-in-user-type package=\"com.android.package2\">\n"
+                + "        <install-in user-type=\"SYSTEM\" />\n"
+                + "        <do-not-install-in user-type=\"PROFILE\" />\n"
+                + "    </install-in-user-type>\n"
+                + "</permissions>";
+
+        final String contents3 =
+                  "<permissions>\n"
+                + "    <install-in-user-type package=\"com.android.package2\">\n"
+                + "        <install-in invalid-attribute=\"ADMIN\" />\n" // Ignore invalid attribute
+                + "    </install-in-user-type>\n"
+                + "    <install-in-user-type package=\"com.android.package2\">\n"
+                + "        <install-in user-type=\"RESTRICTED\" />\n"  // Valid
+                + "    </install-in-user-type>\n"
+                + "    <install-in-user-type>\n" // Ignored since missing package name
+                + "        <install-in user-type=\"ADMIN\" />\n"
+                + "    </install-in-user-type>\n"
+                + "</permissions>";
+
+        Map<String, Set<String>> expectedWhite = new ArrayMap<>();
+        expectedWhite.put("com.android.package1",
+                new ArraySet<>(Arrays.asList("FULL", "PROFILE")));
+        expectedWhite.put("com.android.package2",
+                new ArraySet<>(Arrays.asList("FULL", "PROFILE", "RESTRICTED", "SYSTEM")));
+
+        Map<String, Set<String>> expectedBlack = new ArrayMap<>();
+        expectedBlack.put("com.android.package2",
+                new ArraySet<>(Arrays.asList("GUEST", "PROFILE")));
+
+        final File folder1 = createTempSubfolder("folder1");
+        createTempFile(folder1, "permFile1.xml", contents1);
+
+        final File folder2 = createTempSubfolder("folder2");
+        createTempFile(folder2, "permFile2.xml", contents2);
+
+        // Also, make a third file, but with the name folder1/permFile2.xml, to prove no conflicts.
+        createTempFile(folder1, "permFile2.xml", contents3);
+
+        mSysConfig.readPermissions(folder1, /* No permission needed anyway */ 0);
+        mSysConfig.readPermissions(folder2, /* No permission needed anyway */ 0);
+
+        Map<String, Set<String>> actualWhite = mSysConfig.getAndClearPackageToUserTypeWhitelist();
+        Map<String, Set<String>> actualBlack = mSysConfig.getAndClearPackageToUserTypeBlacklist();
+
+        assertEquals("Whitelist was not cleared", 0,
+                mSysConfig.getAndClearPackageToUserTypeWhitelist().size());
+        assertEquals("Blacklist was not cleared", 0,
+                mSysConfig.getAndClearPackageToUserTypeBlacklist().size());
+
+        assertEquals("Incorrect whitelist.", expectedWhite, actualWhite);
+        assertEquals("Incorrect blacklist", expectedBlack, actualBlack);
+    }
+
+    /**
+     * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
+     * @param folderName subdirectory of mTemporaryFolder to put the file, creating if needed
+     * @return the folder
+     */
+    private File createTempSubfolder(String folderName)
+            throws IOException {
+        File folder = new File(mTemporaryFolder.getRoot(), folderName);
+        folder.mkdir();
+        return folder;
+    }
+
+    /**
+     * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
+     * @param folder pre-existing subdirectory of mTemporaryFolder to put the file
+     * @param fileName name of the file (e.g. filename.xml) to create
+     * @param contents contents to write to the file
+     * @return the folder containing the newly created file (not the file itself!)
+     */
+    private File createTempFile(File folder, String fileName, String contents)
+            throws IOException {
+        File file = new File(folder, fileName);
+        BufferedWriter bw = new BufferedWriter(new FileWriter(file));
+        bw.write(contents);
+        bw.close();
+
+        // Print to logcat for test debugging.
+        Log.d(LOG_TAG, "Contents of file " + file.getAbsolutePath());
+        Scanner input = new Scanner(file);
+        while (input.hasNextLine()) {
+            Log.d(LOG_TAG, input.nextLine());
+        }
+
+        return folder;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
new file mode 100644
index 0000000..f0b0328
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
@@ -0,0 +1,396 @@
+/*
+ * Copyright (C) 2019 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 android.content.pm.UserInfo.FLAG_FULL;
+import static android.content.pm.UserInfo.FLAG_GUEST;
+import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
+import static android.content.pm.UserInfo.FLAG_SYSTEM;
+
+import static com.android.server.pm.UserSystemPackageInstaller.PACKAGE_WHITELIST_MODE_PROP;
+import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT;
+import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE;
+import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE;
+import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST;
+import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_LOG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.content.pm.UserInfo;
+import android.os.Looper;
+import android.os.SystemProperties;
+import android.os.UserManager;
+import android.os.UserManagerInternal;
+import android.support.test.uiautomator.UiDevice;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Tests for UserSystemPackageInstaller.
+ *
+ * <p>Run with:<pre>
+ * atest com.android.server.pm.UserSystemPackageInstallerTest
+ * </pre>
+ */
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class UserSystemPackageInstallerTest {
+    private static final String TAG = "UserSystemPackageInstallerTest";
+
+    private UserSystemPackageInstaller mUserSystemPackageInstaller;
+
+    private Context mContext;
+
+    /** Any users created during this test, for them to be removed when it's done. */
+    private final List<Integer> mRemoveUsers = new ArrayList<>();
+    /** Original value of PACKAGE_WHITELIST_MODE_PROP before the test, to reset at end. */
+    private final int mOriginalWhitelistMode = SystemProperties.getInt(
+            PACKAGE_WHITELIST_MODE_PROP, USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT);
+
+    @Before
+    public void setup() {
+        // Currently UserManagerService cannot be instantiated twice inside a VM without a cleanup
+        // TODO: Remove once UMS supports proper dependency injection
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        LocalServices.removeServiceForTest(UserManagerInternal.class);
+        UserManagerService ums = new UserManagerService(InstrumentationRegistry.getContext());
+
+        mUserSystemPackageInstaller = new UserSystemPackageInstaller(ums);
+        mContext = InstrumentationRegistry.getTargetContext();
+    }
+
+    @After
+    public void tearDown() {
+        UserManager um = UserManager.get(mContext);
+        for (int userId : mRemoveUsers) {
+            um.removeUser(userId);
+        }
+        setUserTypePackageWhitelistMode(mOriginalWhitelistMode);
+    }
+
+    /**
+     * Subclass of SystemConfig without running the constructor.
+     */
+    private class SystemConfigTestClass extends SystemConfig {
+        SystemConfigTestClass(boolean readPermissions) {
+            super(readPermissions);
+        }
+    }
+
+    /**
+     * Test that determineWhitelistedPackagesForUserTypes reads SystemConfig information properly.
+     */
+    @Test
+    public void testDetermineWhitelistedPackagesForUserTypes() {
+        SystemConfig sysConfig = new SystemConfigTestClass(false) {
+            @Override
+            public ArrayMap<String, Set<String>> getAndClearPackageToUserTypeWhitelist() {
+                ArrayMap<String, Set<String>> r = new ArrayMap<>();
+                r.put("com.android.package1", new ArraySet<>(Arrays.asList(
+                        "PROFILE", "SYSTEM", "GUEST", "FULL", "invalid-garbage1")));
+                r.put("com.android.package2", new ArraySet<>(Arrays.asList(
+                        "MANAGED_PROFILE")));
+                return r;
+            }
+
+            @Override
+            public ArrayMap<String, Set<String>> getAndClearPackageToUserTypeBlacklist() {
+                ArrayMap<String, Set<String>> r = new ArrayMap<>();
+                r.put("com.android.package1", new ArraySet<>(Arrays.asList(
+                        "FULL", "RESTRICTED", "invalid-garbage2")));
+                return r;
+            }
+        };
+
+        final ArrayMap<String, Integer> expectedOutput = getNewPackageToWhitelistedFlagsMap();
+        expectedOutput.put("com.android.package1",
+                UserInfo.PROFILE_FLAGS_MASK | FLAG_SYSTEM | FLAG_GUEST);
+        expectedOutput.put("com.android.package2",
+                UserInfo.FLAG_MANAGED_PROFILE);
+
+        final ArrayMap<String, Integer> actualOutput =
+                mUserSystemPackageInstaller.determineWhitelistedPackagesForUserTypes(sysConfig);
+
+        assertEquals("Incorrect package-to-user mapping.", expectedOutput, actualOutput);
+    }
+
+    /**
+     * Test that determineWhitelistedPackagesForUserTypes does not include packages that were never
+     * whitelisted properly, but does include packages that were whitelisted but then blacklisted.
+     */
+    @Test
+    public void testDetermineWhitelistedPackagesForUserTypes_noNetWhitelisting() {
+        SystemConfig sysConfig = new SystemConfigTestClass(false) {
+            @Override
+            public ArrayMap<String, Set<String>> getAndClearPackageToUserTypeWhitelist() {
+                ArrayMap<String, Set<String>> r = new ArrayMap<>();
+                r.put("com.android.package1", new ArraySet<>(Arrays.asList("invalid1")));
+                // com.android.package2 has no whitelisting
+                r.put("com.android.package3", new ArraySet<>(Arrays.asList("PROFILE", "FULL")));
+                r.put("com.android.package4", new ArraySet<>(Arrays.asList("PROFILE")));
+                r.put("com.android.package5", new ArraySet<>());
+                // com.android.package6 has no whitelisting
+                return r;
+            }
+
+            @Override
+            public ArrayMap<String, Set<String>> getAndClearPackageToUserTypeBlacklist() {
+                ArrayMap<String, Set<String>> r = new ArrayMap<>();
+                // com.android.package1 has no blacklisting
+                r.put("com.android.package2", new ArraySet<>(Arrays.asList("FULL")));
+                r.put("com.android.package3", new ArraySet<>(Arrays.asList("PROFILE", "FULL")));
+                r.put("com.android.package4", new ArraySet<>(Arrays.asList("PROFILE", "invalid4")));
+                // com.android.package5 has no blacklisting
+                r.put("com.android.package6", new ArraySet<>(Arrays.asList("invalid6")));
+                return r;
+            }
+        };
+
+        final ArrayMap<String, Integer> expectedOutput = getNewPackageToWhitelistedFlagsMap();
+        expectedOutput.put("com.android.package3", 0);
+        expectedOutput.put("com.android.package4", 0);
+
+        final ArrayMap<String, Integer> actualOutput =
+                mUserSystemPackageInstaller.determineWhitelistedPackagesForUserTypes(sysConfig);
+
+        assertEquals("Incorrect package-to-user mapping.", expectedOutput, actualOutput);
+    }
+
+    /**
+     * Tests that shouldInstallPackage correctly determines which packages should be installed.
+     */
+    @Test
+    public void testShouldInstallPackage() {
+        final String packageName1 = "pkg1"; // whitelisted
+        final String packageName2 = "pkg2"; // whitelisted and blacklisted
+        final String packageName3 = "pkg3"; // whitelisted for a different user type
+        final String packageName4 = "pkg4"; // not whitelisted nor blacklisted at all
+
+        final ArrayMap<String, Integer> pkgFlgMap = new ArrayMap<>(); // Whitelist: pkgs per flags
+        pkgFlgMap.put(packageName1, FLAG_FULL);
+        pkgFlgMap.put(packageName2, 0);
+        pkgFlgMap.put(packageName3, FLAG_MANAGED_PROFILE);
+
+        // Whitelist of pkgs for this specific user, i.e. subset of pkgFlagMap for this user.
+        final Set<String> userWhitelist = new ArraySet<>();
+        userWhitelist.add(packageName1);
+
+        final UserSystemPackageInstaller uspi = new UserSystemPackageInstaller(null, pkgFlgMap);
+
+        final PackageParser.Package pkg1 = new PackageParser.Package(packageName1);
+        final PackageParser.Package pkg2 = new PackageParser.Package(packageName2);
+        final PackageParser.Package pkg3 = new PackageParser.Package(packageName3);
+        final PackageParser.Package pkg4 = new PackageParser.Package(packageName4);
+
+        // No implicit whitelist, so only install pkg1.
+        boolean implicit = false;
+        boolean isSysUser = false;
+        assertTrue(uspi.shouldInstallPackage(pkg1, pkgFlgMap, userWhitelist, implicit, isSysUser));
+        assertFalse(uspi.shouldInstallPackage(pkg2, pkgFlgMap, userWhitelist, implicit, isSysUser));
+        assertFalse(uspi.shouldInstallPackage(pkg3, pkgFlgMap, userWhitelist, implicit, isSysUser));
+        assertFalse(uspi.shouldInstallPackage(pkg4, pkgFlgMap, userWhitelist, implicit, isSysUser));
+
+        // Use implicit whitelist, so install pkg1 and pkg4
+        implicit = true;
+        isSysUser = false;
+        assertTrue(uspi.shouldInstallPackage(pkg1, pkgFlgMap, userWhitelist, implicit, isSysUser));
+        assertFalse(uspi.shouldInstallPackage(pkg2, pkgFlgMap, userWhitelist, implicit, isSysUser));
+        assertFalse(uspi.shouldInstallPackage(pkg3, pkgFlgMap, userWhitelist, implicit, isSysUser));
+        assertTrue(uspi.shouldInstallPackage(pkg4, pkgFlgMap, userWhitelist, implicit, isSysUser));
+
+        // For user 0 specifically, we always implicitly whitelist.
+        implicit = false;
+        isSysUser = true;
+        assertTrue(uspi.shouldInstallPackage(pkg1, pkgFlgMap, userWhitelist, implicit, isSysUser));
+        assertFalse(uspi.shouldInstallPackage(pkg2, pkgFlgMap, userWhitelist, implicit, isSysUser));
+        assertFalse(uspi.shouldInstallPackage(pkg3, pkgFlgMap, userWhitelist, implicit, isSysUser));
+        assertTrue(uspi.shouldInstallPackage(pkg4, pkgFlgMap, userWhitelist, implicit, isSysUser));
+    }
+
+    /**
+     * Tests that getWhitelistedPackagesForUserType works properly, assuming that
+     * mWhitelistedPackagesForUserTypes (i.e. determineWhitelistedPackagesForUserTypes) is correct.
+     */
+    @Test
+    public void testGetWhitelistedPackagesForUserType() {
+        final String packageName1 = "pkg1"; // whitelisted for FULL
+        final String packageName2 = "pkg2"; // blacklisted whenever whitelisted
+        final String packageName3 = "pkg3"; // whitelisted for SYSTEM
+        final String packageName4 = "pkg4"; // whitelisted for FULL
+
+        final ArrayMap<String, Integer> pkgFlagMap = new ArrayMap<>(); // Whitelist: pkgs per flags
+        pkgFlagMap.put(packageName1, FLAG_FULL);
+        pkgFlagMap.put(packageName2, 0);
+        pkgFlagMap.put(packageName3, FLAG_SYSTEM);
+        pkgFlagMap.put(packageName4, FLAG_FULL);
+
+        // Whitelist of pkgs for this specific user, i.e. subset of pkgFlagMap for this user.
+        final Set<String> expectedUserWhitelist = new ArraySet<>();
+        expectedUserWhitelist.add(packageName1);
+
+        UserSystemPackageInstaller uspi = new UserSystemPackageInstaller(null, pkgFlagMap);
+
+        Set<String> output = uspi.getWhitelistedPackagesForUserType(FLAG_FULL);
+        assertEquals("Whitelist for FULL is the wrong size", 2, output.size());
+        assertTrue("Whitelist for FULL doesn't contain pkg1", output.contains(packageName1));
+        assertTrue("Whitelist for FULL doesn't contain pkg4", output.contains(packageName4));
+
+        output = uspi.getWhitelistedPackagesForUserType(FLAG_SYSTEM);
+        assertEquals("Whitelist for SYSTEM is the wrong size", 1, output.size());
+        assertTrue("Whitelist for SYSTEM doesn't contain pkg1", output.contains(packageName3));
+    }
+
+    /**
+     * Test that a newly created FULL user has the expected system packages.
+     *
+     * Assumes that SystemConfig and UserManagerService.determineWhitelistedPackagesForUserTypes
+     * work correctly (they are tested separately).
+     */
+    @Test
+    public void testPackagesForCreateUser_full() {
+        final int userFlags = UserInfo.FLAG_FULL;
+        setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE);
+        PackageManager pm = mContext.getPackageManager();
+
+        final SystemConfig sysConfig = new SystemConfigTestClass(true);
+        final ArrayMap<String, Integer> packageMap =
+                mUserSystemPackageInstaller.determineWhitelistedPackagesForUserTypes(sysConfig);
+        final Set<String> expectedPackages = new ArraySet<>(packageMap.size());
+        for (int i = 0; i < packageMap.size(); i++) {
+            if ((userFlags & packageMap.valueAt(i)) != 0) {
+                expectedPackages.add(packageMap.keyAt(i));
+            }
+        }
+
+        final UserManager um = UserManager.get(mContext);
+        final UserInfo user = um.createUser("Test User", userFlags);
+        assertNotNull(user);
+        mRemoveUsers.add(user.id);
+
+        final List<PackageInfo> packageInfos = pm.getInstalledPackagesAsUser(
+                PackageManager.MATCH_SYSTEM_ONLY
+                        | PackageManager.MATCH_DISABLED_COMPONENTS
+                        | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS,
+                user.id);
+        final Set<String> actualPackages = new ArraySet<>(packageInfos.size());
+        for (PackageInfo p : packageInfos) {
+            actualPackages.add(p.packageName);
+        }
+        checkPackageDifferences(expectedPackages, actualPackages);
+    }
+
+    /** Asserts that actual is a subset of expected. */
+    private void checkPackageDifferences(Set<String> expected, Set<String> actual) {
+        final Set<String> uniqueToExpected = new ArraySet<>(expected);
+        uniqueToExpected.removeAll(actual);
+        final Set<String> uniqueToActual = new ArraySet<>(actual);
+        uniqueToActual.removeAll(expected);
+
+        Log.v(TAG, "Expected list uniquely has " + uniqueToExpected);
+        Log.v(TAG, "Actual list uniquely has " + uniqueToActual);
+
+        assertTrue("User's system packages includes non-whitelisted packages: " + uniqueToActual,
+                uniqueToActual.isEmpty());
+    }
+
+    /**
+     * Test that setEnableUserTypePackageWhitelist() has the correct effect.
+     */
+    @Test
+    public void testSetWhitelistEnabledMode() {
+        setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE);
+        assertFalse(mUserSystemPackageInstaller.isLogMode());
+        assertFalse(mUserSystemPackageInstaller.isEnforceMode());
+        assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+
+        setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_LOG);
+        assertTrue(mUserSystemPackageInstaller.isLogMode());
+        assertFalse(mUserSystemPackageInstaller.isEnforceMode());
+        assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+
+        setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE);
+        assertFalse(mUserSystemPackageInstaller.isLogMode());
+        assertTrue(mUserSystemPackageInstaller.isEnforceMode());
+        assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+
+        setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST);
+        assertFalse(mUserSystemPackageInstaller.isLogMode());
+        assertFalse(mUserSystemPackageInstaller.isEnforceMode());
+        assertTrue(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+
+        setUserTypePackageWhitelistMode(
+                USER_TYPE_PACKAGE_WHITELIST_MODE_LOG | USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE);
+        assertTrue(mUserSystemPackageInstaller.isLogMode());
+        assertTrue(mUserSystemPackageInstaller.isEnforceMode());
+        assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+
+        setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST
+                | USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE);
+        assertFalse(mUserSystemPackageInstaller.isLogMode());
+        assertTrue(mUserSystemPackageInstaller.isEnforceMode());
+        assertTrue(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+    }
+
+    /** Sets the whitelist mode to the desired value via adb's setprop. */
+    private void setUserTypePackageWhitelistMode(int mode) {
+        UiDevice mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        try {
+            String result = mUiDevice.executeShellCommand(String.format("setprop %s %d",
+                    PACKAGE_WHITELIST_MODE_PROP, mode));
+            assertFalse("Failed to set sysprop " + PACKAGE_WHITELIST_MODE_PROP + ": " + result,
+                    result != null && result.contains("Failed"));
+        } catch (IOException e) {
+            fail("Failed to set sysprop " + PACKAGE_WHITELIST_MODE_PROP + ":\n" + e);
+        }
+    }
+
+    private ArrayMap<String, Integer> getNewPackageToWhitelistedFlagsMap() {
+        final ArrayMap<String, Integer> pkgFlagMap = new ArrayMap<>();
+        // "android" is always treated as whitelisted, regardless of the xml file.
+        pkgFlagMap.put("android", FLAG_SYSTEM | UserInfo.FLAG_FULL | UserInfo.PROFILE_FLAGS_MASK);
+        return pkgFlagMap;
+    }
+}