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 @@ public class UserManagerService extends IUserManager.Stub {
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 @@ public class UserManagerService extends IUserManager.Stub {
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 0000000000000..036d1e807f978
--- /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.
+ *
+ * Uses the SystemConfig's install-in-user-type whitelist;
+ * see {@link SystemConfig#getAndClearPackageToUserTypeWhitelist} and
+ * {@link SystemConfig#getAndClearPackageToUserTypeBlacklist}.
+ *
+ *
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
+ *
+ * - Explicitly blacklisted for a particular user type
+ * - Explicitly whitelisted for a particular user type
+ * - Not mentioned at all, for any user type (neither whitelisted nor blacklisted)
+ *
+ * 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:
+ *
+ * - If {@link #isImplicitWhitelistMode()}, the package is implicitly treated as whitelisted
+ * for all users
+ * - Otherwise, the package is implicitly treated as blacklisted for all non-SYSTEM users
+ * - Either way, for {@link UserHandle#USER_SYSTEM}, the package will be implicitly
+ * whitelisted so that it can be used for local development purposes.
+ *
+ */
+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.
+ *
+ * - 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) - implicitly whitelist any package not mentioned in the whitelist
+ * - -1 - use device default (as defined in res/res/values/config.xml)
+ *
+ * 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.
+ * 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 mWhitelitsedPackagesForUserTypes;
+
+ private final UserManagerService mUm;
+
+ UserSystemPackageInstaller(UserManagerService ums) {
+ mUm = ums;
+ mWhitelitsedPackagesForUserTypes =
+ determineWhitelistedPackagesForUserTypes(SystemConfig.getInstance());
+ }
+
+ /** Constructor for testing purposes. */
+ @VisibleForTesting
+ UserSystemPackageInstaller(UserManagerService ums, ArrayMap 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 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 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 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 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 whitelistedPackages = getWhitelistedPackagesForUserType(flags);
+
+ final Set 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 userTypeWhitelist,
+ @NonNull Set 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 getWhitelistedPackagesForUserType(int flags) {
+ Set 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 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:
+ *
+ * - Illegal user types are ignored.
+ * - Packages that never whitelisted at all (even if they are explicitly blacklisted) are
+ * ignored.
+ * - 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).
+ *
+ */
+ @VisibleForTesting
+ static ArrayMap determineWhitelistedPackagesForUserTypes(
+ SystemConfig sysConfig) {
+
+ final ArrayMap> whitelist =
+ sysConfig.getAndClearPackageToUserTypeWhitelist();
+ // result maps packageName -> userTypes on which the package should be installed.
+ final ArrayMap 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> 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 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 0000000000000..ff03391ea0310
--- /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 =
+ "\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + "";
+
+ final String contents2 =
+ "\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + "";
+
+ final String contents3 =
+ "\n"
+ + " \n"
+ + " \n" // Ignore invalid attribute
+ + " \n"
+ + " \n"
+ + " \n" // Valid
+ + " \n"
+ + " \n" // Ignored since missing package name
+ + " \n"
+ + " \n"
+ + "";
+
+ Map> 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> 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> actualWhite = mSysConfig.getAndClearPackageToUserTypeWhitelist();
+ Map> 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 0000000000000..f0b0328ff7d46
--- /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.
+ *
+ * Run with:
+ * atest com.android.server.pm.UserSystemPackageInstallerTest
+ *
+ */
+@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 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> getAndClearPackageToUserTypeWhitelist() {
+ ArrayMap> 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> getAndClearPackageToUserTypeBlacklist() {
+ ArrayMap> r = new ArrayMap<>();
+ r.put("com.android.package1", new ArraySet<>(Arrays.asList(
+ "FULL", "RESTRICTED", "invalid-garbage2")));
+ return r;
+ }
+ };
+
+ final ArrayMap 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 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> getAndClearPackageToUserTypeWhitelist() {
+ ArrayMap> 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> getAndClearPackageToUserTypeBlacklist() {
+ ArrayMap> 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 expectedOutput = getNewPackageToWhitelistedFlagsMap();
+ expectedOutput.put("com.android.package3", 0);
+ expectedOutput.put("com.android.package4", 0);
+
+ final ArrayMap 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 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 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 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 expectedUserWhitelist = new ArraySet<>();
+ expectedUserWhitelist.add(packageName1);
+
+ UserSystemPackageInstaller uspi = new UserSystemPackageInstaller(null, pkgFlagMap);
+
+ Set 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 packageMap =
+ mUserSystemPackageInstaller.determineWhitelistedPackagesForUserTypes(sysConfig);
+ final Set 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 packageInfos = pm.getInstalledPackagesAsUser(
+ PackageManager.MATCH_SYSTEM_ONLY
+ | PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS,
+ user.id);
+ final Set 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 expected, Set actual) {
+ final Set uniqueToExpected = new ArraySet<>(expected);
+ uniqueToExpected.removeAll(actual);
+ final Set 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 getNewPackageToWhitelistedFlagsMap() {
+ final ArrayMap 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;
+ }
+}