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/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);
+        }
+    }
+}