Merge "Add State to statsd config language"
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/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 05ff490..c79b0ca 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -74,6 +74,7 @@
         "src/external/StatsPuller.cpp",
         "src/external/StatsPullerManager.cpp",
         "src/external/SubsystemSleepStatePuller.cpp",
+        "src/external/SurfaceflingerStatsPuller.cpp",
         "src/external/TrainInfoPuller.cpp",
         "src/FieldValue.cpp",
         "src/guardrail/StatsdStats.cpp",
@@ -138,6 +139,7 @@
         "libservices",
         "libstatslog",
         "libsysutils",
+        "libtimestats_proto",
         "libutils",
     ],
 }
@@ -239,6 +241,7 @@
         "tests/external/IncidentReportArgs_test.cpp",
         "tests/external/puller_util_test.cpp",
         "tests/external/StatsPuller_test.cpp",
+        "tests/external/SurfaceflingerStatsPuller_test.cpp",
         "tests/FieldValue_test.cpp",
         "tests/guardrail/StatsdStats_test.cpp",
         "tests/indexed_priority_queue_test.cpp",
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index f69e2d0..7a183a3 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -17,12 +17,17 @@
 #define DEBUG false
 #include "Log.h"
 
+#include "StatsPullerManager.h"
+
 #include <android/os/IStatsCompanionService.h>
 #include <android/os/IStatsPullerCallback.h>
 #include <cutils/log.h>
 #include <math.h>
 #include <stdint.h>
+
 #include <algorithm>
+#include <iostream>
+
 #include "../StatsService.h"
 #include "../logd/LogEvent.h"
 #include "../stats_log_util.h"
@@ -32,13 +37,11 @@
 #include "ResourceHealthManagerPuller.h"
 #include "StatsCallbackPuller.h"
 #include "StatsCompanionServicePuller.h"
-#include "StatsPullerManager.h"
 #include "SubsystemSleepStatePuller.h"
+#include "SurfaceflingerStatsPuller.h"
 #include "TrainInfoPuller.h"
 #include "statslog.h"
 
-#include <iostream>
-
 using std::make_shared;
 using std::map;
 using std::shared_ptr;
@@ -269,6 +272,10 @@
         // App ops
         {android::util::APP_OPS,
          {.puller = new StatsCompanionServicePuller(android::util::APP_OPS)}},
+        // SurfaceflingerStatsGlobalInfo
+        {android::util::SURFACEFLINGER_STATS_GLOBAL_INFO,
+         {.puller =
+                  new SurfaceflingerStatsPuller(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO)}},
 };
 
 StatsPullerManager::StatsPullerManager() : mNextPullTimeNs(NO_ALARM_UPDATE) {
diff --git a/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp b/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp
new file mode 100644
index 0000000..23b2236
--- /dev/null
+++ b/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright 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.
+ */
+
+#include "SurfaceflingerStatsPuller.h"
+
+#include <cutils/compiler.h>
+
+#include <numeric>
+
+#include "logd/LogEvent.h"
+#include "stats_log_util.h"
+#include "statslog.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+SurfaceflingerStatsPuller::SurfaceflingerStatsPuller(const int tagId) : StatsPuller(tagId) {
+}
+
+bool SurfaceflingerStatsPuller::PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) {
+    switch (mTagId) {
+        case android::util::SURFACEFLINGER_STATS_GLOBAL_INFO:
+            return pullGlobalInfo(data);
+        default:
+            break;
+    }
+
+    return false;
+}
+
+static int64_t getTotalTime(
+        const google::protobuf::RepeatedPtrField<surfaceflinger::SFTimeStatsHistogramBucketProto>&
+                buckets) {
+    int64_t total = 0;
+    for (const auto& bucket : buckets) {
+        if (bucket.time_millis() == 1000) {
+            continue;
+        }
+
+        total += bucket.time_millis() * bucket.frame_count();
+    }
+
+    return total;
+}
+
+bool SurfaceflingerStatsPuller::pullGlobalInfo(std::vector<std::shared_ptr<LogEvent>>* data) {
+    std::string protoBytes;
+    if (CC_UNLIKELY(mStatsProvider)) {
+        protoBytes = mStatsProvider();
+    } else {
+        std::unique_ptr<FILE, decltype(&pclose)> pipe(popen("dumpsys SurfaceFlinger --timestats -dump --proto", "r"), pclose);
+        if (!pipe.get()) {
+            return false;
+        }
+        char buf[1024];
+        size_t bytesRead = 0;
+        do {
+            bytesRead = fread(buf, 1, sizeof(buf), pipe.get());
+            protoBytes.append(buf, bytesRead);
+        } while (bytesRead > 0);
+    }
+    surfaceflinger::SFTimeStatsGlobalProto proto;
+    proto.ParseFromString(protoBytes);
+
+    int64_t totalTime = getTotalTime(proto.present_to_present());
+
+    data->clear();
+    data->reserve(1);
+    std::shared_ptr<LogEvent> event =
+            make_shared<LogEvent>(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO, getWallClockNs(),
+                                  getElapsedRealtimeNs());
+    if (!event->write(proto.total_frames())) return false;
+    if (!event->write(proto.missed_frames())) return false;
+    if (!event->write(proto.client_composition_frames())) return false;
+    if (!event->write(proto.display_on_time())) return false;
+    if (!event->write(totalTime)) return false;
+    event->init();
+    data->emplace_back(event);
+
+    return true;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/external/SurfaceflingerStatsPuller.h b/cmds/statsd/src/external/SurfaceflingerStatsPuller.h
new file mode 100644
index 0000000..ed7153e
--- /dev/null
+++ b/cmds/statsd/src/external/SurfaceflingerStatsPuller.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 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.
+ */
+
+#pragma once
+
+#include <timestatsproto/TimeStatsProtoHeader.h>
+
+#include "StatsPuller.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * Pull metrics from Surfaceflinger
+ */
+class SurfaceflingerStatsPuller : public StatsPuller {
+public:
+    explicit SurfaceflingerStatsPuller(const int tagId);
+
+    // StatsPuller interface
+    bool PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) override;
+
+protected:
+    // Test-only, for injecting fake data
+    using StatsProvider = std::function<std::string()>;
+    StatsProvider mStatsProvider;
+
+private:
+    bool pullGlobalInfo(std::vector<std::shared_ptr<LogEvent>>* data);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index fe25a25..76ee9a6 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -1713,6 +1713,11 @@
     EXPECT_EQ(kActive, activation1004->state);
     EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation1004->activationType);
     // }}}------------------------------------------------------------------------------
+
+    // Clear the data stored on disk as a result of the system server death.
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey1, configAddedTimeNs + NS_PER_SEC, false, true,
+                                ADB_DUMP, FAST, &buffer);
 }
 
 #else
diff --git a/cmds/statsd/tests/e2e/ConfigTtl_e2e_test.cpp b/cmds/statsd/tests/e2e/ConfigTtl_e2e_test.cpp
index b98dc60..325e869 100644
--- a/cmds/statsd/tests/e2e/ConfigTtl_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/ConfigTtl_e2e_test.cpp
@@ -96,6 +96,11 @@
 
     EXPECT_EQ((int64_t)(bucketStartTimeNs + 25 * bucketSizeNs + 2 + 2 * 3600 * NS_PER_SEC),
               processor->mMetricsManagers.begin()->second->getTtlEndNs());
+
+    // Clear the data stored on disk as a result of the ttl.
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 25 * bucketSizeNs + 3, false, true,
+                                ADB_DUMP, FAST, &buffer);
 }
 
 
diff --git a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp b/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
new file mode 100644
index 0000000..5c9636f
--- /dev/null
+++ b/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "SurfaceflingerStatsPuller_test"
+
+#include "src/external/SurfaceflingerStatsPuller.h"
+
+#include <gtest/gtest.h>
+#include <log/log.h>
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class TestableSurfaceflingerStatsPuller : public SurfaceflingerStatsPuller {
+public:
+    TestableSurfaceflingerStatsPuller(const int tagId) : SurfaceflingerStatsPuller(tagId){};
+
+    void injectStats(const StatsProvider& statsProvider) {
+        mStatsProvider = statsProvider;
+    }
+};
+
+class SurfaceflingerStatsPullerTest : public ::testing::Test {
+public:
+    SurfaceflingerStatsPullerTest() {
+        const ::testing::TestInfo* const test_info =
+                ::testing::UnitTest::GetInstance()->current_test_info();
+        ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
+    }
+
+    ~SurfaceflingerStatsPullerTest() {
+        const ::testing::TestInfo* const test_info =
+                ::testing::UnitTest::GetInstance()->current_test_info();
+        ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
+    }
+};
+
+TEST_F(SurfaceflingerStatsPullerTest, pullGlobalStats) {
+    surfaceflinger::SFTimeStatsGlobalProto proto;
+    proto.set_total_frames(1);
+    proto.set_missed_frames(2);
+    proto.set_client_composition_frames(2);
+    proto.set_display_on_time(4);
+
+    auto bucketOne = proto.add_present_to_present();
+    bucketOne->set_time_millis(2);
+    bucketOne->set_frame_count(4);
+    auto bucketTwo = proto.add_present_to_present();
+    bucketTwo->set_time_millis(4);
+    bucketTwo->set_frame_count(1);
+    auto bucketThree = proto.add_present_to_present();
+    bucketThree->set_time_millis(1000);
+    bucketThree->set_frame_count(1);
+    static constexpr int64_t expectedAnimationMillis = 12;
+    TestableSurfaceflingerStatsPuller puller(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO);
+
+    puller.injectStats([&] {
+        return proto.SerializeAsString();
+    });
+    puller.ForceClearCache();
+    vector<std::shared_ptr<LogEvent>> outData;
+    puller.Pull(&outData);
+
+    ASSERT_EQ(1, outData.size());
+    EXPECT_EQ(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO, outData[0]->GetTagId());
+    EXPECT_EQ(proto.total_frames(), outData[0]->getValues()[0].mValue.long_value);
+    EXPECT_EQ(proto.missed_frames(), outData[0]->getValues()[1].mValue.long_value);
+    EXPECT_EQ(proto.client_composition_frames(), outData[0]->getValues()[2].mValue.long_value);
+    EXPECT_EQ(proto.display_on_time(), outData[0]->getValues()[3].mValue.long_value);
+    EXPECT_EQ(expectedAnimationMillis, outData[0]->getValues()[4].mValue.long_value);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 3e4649f..f28b85c 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/internal/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl
index 9049c3a..e415b41 100644
--- a/core/java/com/android/internal/compat/IPlatformCompat.aidl
+++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl
@@ -33,15 +33,30 @@
      * Reports that a compatibility change is affecting an app process now.
      *
      * <p>Note: for changes that are gated using {@link #isChangeEnabled(long, ApplicationInfo)},
-     * you do not need to call this API directly. The change will be reported for you in the case
-     * that {@link #isChangeEnabled(long, ApplicationInfo)} returns {@code true}.
+     * you do not need to call this API directly. The change will be reported for you.
      *
      * @param changeId The ID of the compatibility change taking effect.
-     * @param appInfo Representing the affected app.
+     * @param appInfo  Representing the affected app.
      */
     void reportChange(long changeId, in ApplicationInfo appInfo);
 
     /**
+     * Reports that a compatibility change is affecting an app process now.
+     *
+     * <p>Same as {@link #reportChange(long, ApplicationInfo)}, except it receives a package name
+     * instead of an {@link ApplicationInfo}
+     * object, and finds an app info object based on the package name. Returns {@code true} if
+     * there is no installed package by that name.
+     *
+     * <p>Note: for changes that are gated using {@link #isChangeEnabled(long, String)},
+     * you do not need to call this API directly. The change will be reported for you.
+     *
+     * @param changeId    The ID of the compatibility change taking effect.
+     * @param packageName The package name of the app in question.
+     */
+     void reportChangeByPackageName(long changeId, in String packageName);
+
+    /**
      * Query if a given compatibility change is enabled for an app process. This method should
      * be called when implementing functionality on behalf of the affected app.
      *
@@ -49,13 +64,35 @@
      * change, resulting in differing behaviour compared to earlier releases. If this method returns
      * {@code false}, the calling code should behave as it did in earlier releases.
      *
-     * <p>When this method returns {@code true}, it will also report the change as
-     * {@link #reportChange(long, ApplicationInfo)} would, so there is no need to call that method
-     * directly.
+     * <p>It will also report the change as {@link #reportChange(long, ApplicationInfo)} would, so
+     * there is no need to call that method directly.
      *
      * @param changeId The ID of the compatibility change in question.
-     * @param appInfo Representing the app in question.
+     * @param appInfo  Representing the app in question.
      * @return {@code true} if the change is enabled for the current app.
      */
     boolean isChangeEnabled(long changeId, in ApplicationInfo appInfo);
+
+    /**
+     * Query if a given compatibility change is enabled for an app process. This method should
+     * be called when implementing functionality on behalf of the affected app.
+     *
+     * <p>Same as {@link #isChangeEnabled(long, ApplicationInfo)}, except it receives a package name
+     * instead of an {@link ApplicationInfo}
+     * object, and finds an app info object based on the package name. Returns {@code true} if
+     * there is no installed package by that name.
+     *
+     * <p>If this method returns {@code true}, the calling code should implement the compatibility
+     * change, resulting in differing behaviour compared to earlier releases. If this method
+     * returns
+     * {@code false}, the calling code should behave as it did in earlier releases.
+     *
+     * <p>It will also report the change as {@link #reportChange(long, String)} would, so there is
+     * no need to call that method directly.
+     *
+     * @param changeId    The ID of the compatibility change in question.
+     * @param packageName The package name of the app in question.
+     * @return {@code true} if the change is enabled for the current app.
+     */
+    boolean isChangeEnabledByPackageName(long changeId, in String packageName);
 }
\ No newline at end of file
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/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b4374fe..d46fe8d 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -696,26 +696,32 @@
     // Read if we are using the profile configuration, do this at the start since the last ART args
     // take precedence.
     property_get("dalvik.vm.profilebootclasspath", propBuf, "");
-    std::string profile_boot_class_path = propBuf;
+    std::string profile_boot_class_path_flag = propBuf;
     // Empty means the property is unset and we should default to the phenotype property.
     // The possible values are {"true", "false", ""}
-    if (profile_boot_class_path.empty()) {
-        profile_boot_class_path = server_configurable_flags::GetServerConfigurableFlag(
+    if (profile_boot_class_path_flag.empty()) {
+        profile_boot_class_path_flag = server_configurable_flags::GetServerConfigurableFlag(
                 RUNTIME_NATIVE_BOOT_NAMESPACE,
                 PROFILE_BOOT_CLASS_PATH,
                 /*default_value=*/ "");
     }
-    if (profile_boot_class_path == "true") {
+    const bool profile_boot_class_path = (profile_boot_class_path_flag == "true");
+    if (profile_boot_class_path) {
+        addOption("-Xcompiler-option");
+        addOption("--count-hotness-in-compiled-code");
         addOption("-Xps-profile-boot-class-path");
         addOption("-Xps-profile-aot-code");
         addOption("-Xjitsaveprofilinginfo");
     }
 
-    std::string use_apex_image =
+    std::string use_apex_image_flag =
         server_configurable_flags::GetServerConfigurableFlag(RUNTIME_NATIVE_BOOT_NAMESPACE,
                                                              ENABLE_APEX_IMAGE,
                                                              /*default_value=*/ "");
-    if (use_apex_image == "true") {
+    // Use the APEX boot image for boot class path profiling to get JIT samples on BCP methods.
+    // Also use the APEX boot image if it's explicitly enabled via configuration flag.
+    const bool use_apex_image = profile_boot_class_path || (use_apex_image_flag == "true");
+    if (use_apex_image) {
         addOption(kApexImageOption);
         ALOGI("Using Apex boot image: '%s'\n", kApexImageOption);
     } else if (parseRuntimeOption("dalvik.vm.boot-image", bootImageBuf, "-Ximage:")) {
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 7e167c9..befa637 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/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 72ada6e..8240345 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -126,8 +126,6 @@
     static final int DISMISS_GROUP_CANCELLED = 9;
     static final int DISMISS_INVALID_INTENT = 10;
 
-    public static final int MAX_BUBBLES = 5; // TODO: actually enforce this
-
     private final Context mContext;
     private final NotificationEntryManager mNotificationEntryManager;
     private final BubbleTaskStackListener mTaskStackListener;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 31cf853..340dced 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -181,7 +181,9 @@
      */
     private float mVerticalPosPercentBeforeRotation = -1;
 
+    private int mMaxBubbles;
     private int mBubbleSize;
+    private int mBubbleElevation;
     private int mBubblePaddingTop;
     private int mBubbleTouchPadding;
     private int mExpandedViewPadding;
@@ -326,7 +328,9 @@
         mInflater = LayoutInflater.from(context);
 
         Resources res = getResources();
+        mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
         mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
+        mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
         mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
         mBubbleTouchPadding = res.getDimensionPixelSize(R.dimen.bubble_touch_padding);
         mExpandedAnimateXDistance =
@@ -1597,8 +1601,7 @@
         for (int i = 0; i < bubbleCount; i++) {
             BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
             bv.updateDotVisibility(true /* animate */);
-            bv.setZ((BubbleController.MAX_BUBBLES
-                    * getResources().getDimensionPixelSize(R.dimen.bubble_elevation)) - i);
+            bv.setZ((mMaxBubbles * mBubbleElevation) - i);
             // If the dot is on the left, and so is the stack, we need to change the dot position.
             if (bv.getDotPositionOnLeft() == mStackOnLeftOrWillBe) {
                 bv.setDotPosition(!mStackOnLeftOrWillBe, animate);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 1400921..6c0f90a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -21,6 +21,7 @@
 import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
 import static com.android.systemui.statusbar.phone.StatusBar.ENABLE_CHILD_NOTIFICATIONS;
 
+import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.NotificationManager;
 import android.content.ComponentName;
@@ -57,8 +58,9 @@
     private final NotificationGroupManager mGroupManager =
             Dependency.get(NotificationGroupManager.class);
 
-    private final ArrayList<NotificationSettingsListener> mSettingsListeners = new ArrayList<>();
     private final Context mContext;
+    private final ArrayList<NotificationSettingsListener> mSettingsListeners = new ArrayList<>();
+    @Nullable private NotifServiceListener mDownstreamListener;
 
     @Inject
     public NotificationListener(Context context) {
@@ -69,6 +71,10 @@
         mSettingsListeners.add(listener);
     }
 
+    public void setDownstreamListener(NotifServiceListener downstreamListener) {
+        mDownstreamListener = downstreamListener;
+    }
+
     @Override
     public void onListenerConnected() {
         if (DEBUG) Log.d(TAG, "onListenerConnected");
@@ -81,6 +87,9 @@
         final RankingMap currentRanking = getCurrentRanking();
         Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
             for (StatusBarNotification sbn : notifications) {
+                if (mDownstreamListener != null) {
+                    mDownstreamListener.onNotificationPosted(sbn, currentRanking);
+                }
                 mEntryManager.addNotification(sbn, currentRanking);
             }
         });
@@ -95,6 +104,11 @@
         if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
             Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
                 processForRemoteInput(sbn.getNotification(), mContext);
+
+                if (mDownstreamListener != null) {
+                    mDownstreamListener.onNotificationPosted(sbn, rankingMap);
+                }
+
                 String key = sbn.getKey();
                 boolean isUpdate =
                         mEntryManager.getNotificationData().get(key) != null;
@@ -133,6 +147,9 @@
         if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
             final String key = sbn.getKey();
             Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
+                if (mDownstreamListener != null) {
+                    mDownstreamListener.onNotificationRemoved(sbn, rankingMap, reason);
+                }
                 mEntryManager.removeNotification(key, rankingMap, reason);
             });
         }
@@ -149,6 +166,9 @@
         if (rankingMap != null) {
             RankingMap r = onPluginRankingUpdate(rankingMap);
             Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
+                if (mDownstreamListener != null) {
+                    mDownstreamListener.onNotificationRankingUpdate(rankingMap);
+                }
                 mEntryManager.updateNotificationRanking(r);
             });
         }
@@ -175,4 +195,12 @@
 
         default void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons) { }
     }
+
+    /** Interface for listening to add/remove events that we receive from NotificationManager. */
+    public interface NotifServiceListener {
+        void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap);
+        void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap);
+        void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap, int reason);
+        void onNotificationRankingUpdate(RankingMap rankingMap);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineInitializer.java
new file mode 100644
index 0000000..df70828
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineInitializer.java
@@ -0,0 +1,68 @@
+/*
+ * 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.systemui.statusbar.notification;
+
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+import com.android.systemui.statusbar.NotificationListener;
+
+import javax.inject.Inject;
+
+/**
+ * Initialization code for the new notification pipeline.
+ */
+public class NotifPipelineInitializer {
+
+    @Inject
+    public NotifPipelineInitializer() {
+    }
+
+    public void initialize(
+            NotificationListener notificationService) {
+
+        // TODO Put real code here
+        notificationService.setDownstreamListener(new NotificationListener.NotifServiceListener() {
+            @Override
+            public void onNotificationPosted(StatusBarNotification sbn,
+                    NotificationListenerService.RankingMap rankingMap) {
+                Log.d(TAG, "onNotificationPosted " + sbn.getKey());
+            }
+
+            @Override
+            public void onNotificationRemoved(StatusBarNotification sbn,
+                    NotificationListenerService.RankingMap rankingMap) {
+                Log.d(TAG, "onNotificationRemoved " + sbn.getKey());
+            }
+
+            @Override
+            public void onNotificationRemoved(StatusBarNotification sbn,
+                    NotificationListenerService.RankingMap rankingMap, int reason) {
+                Log.d(TAG, "onNotificationRemoved " + sbn.getKey());
+            }
+
+            @Override
+            public void onNotificationRankingUpdate(
+                    NotificationListenerService.RankingMap rankingMap) {
+                Log.d(TAG, "onNotificationRankingUpdate");
+            }
+        });
+    }
+
+    private static final String TAG = "NotifInitializer";
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index f06fbbd..377d138 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -197,6 +197,7 @@
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.NotifPipelineInitializer;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationAlertingManager;
 import com.android.systemui.statusbar.notification.NotificationClicker;
@@ -390,6 +391,8 @@
     @Inject
     @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME)
     boolean mAllowNotificationLongPress;
+    @Inject
+    protected NotifPipelineInitializer mNotifPipelineInitializer;
 
     // expanded notifications
     protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
@@ -1128,6 +1131,8 @@
 
         mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager);
         mNotificationListController.bind();
+
+        mNotifPipelineInitializer.initialize(mNotificationListener);
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c07f67b..d7a68a1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -15267,7 +15267,7 @@
                                 mBatteryStatsService.removeUid(uid);
                                 if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
                                     mAppOpsService.resetAllModes(UserHandle.getUserId(uid),
-                                            intent.getData().getSchemeSpecificPart());
+                                            intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME));
                                 } else {
                                     mAppOpsService.uidRemoved(uid);
                                 }
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 81e507c..33d8dec 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.util.Slog;
 import android.util.StatsLog;
 
@@ -49,6 +50,15 @@
     }
 
     @Override
+    public void reportChangeByPackageName(long changeId, String packageName) {
+        ApplicationInfo appInfo = getApplicationInfo(packageName);
+        if (appInfo == null) {
+            return;
+        }
+        reportChange(changeId, appInfo);
+    }
+
+    @Override
     public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) {
         if (CompatConfig.get().isChangeEnabled(changeId, appInfo)) {
             reportChange(changeId, appInfo,
@@ -61,11 +71,29 @@
     }
 
     @Override
+    public boolean isChangeEnabledByPackageName(long changeId, String packageName) {
+        ApplicationInfo appInfo = getApplicationInfo(packageName);
+        if (appInfo == null) {
+            return true;
+        }
+        return isChangeEnabled(changeId, appInfo);
+    }
+
+    @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) return;
         CompatConfig.get().dumpConfig(pw);
     }
 
+    private ApplicationInfo getApplicationInfo(String packageName) {
+        try {
+            return mContext.getPackageManager().getApplicationInfo(packageName, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.e(TAG, "No installed package " + packageName);
+        }
+        return null;
+    }
+
     private void reportChange(long changeId, ApplicationInfo appInfo, int state) {
         int uid = appInfo.uid;
         //TODO(b/138374585): Implement rate limiting for the logs.
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 123e65e..d07e2d2 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);
         }
     }
 
@@ -17746,8 +17712,14 @@
                 }
             }
             if (removedAppId >= 0) {
+                // If a system app's updates are uninstalled the UID is not actually removed. Some
+                // services need to know the package name affected.
+                if (extras.getBoolean(Intent.EXTRA_REPLACING, false)) {
+                    extras.putString(Intent.EXTRA_PACKAGE_NAME, removedPackage);
+                }
+
                 packageSender.sendPackageBroadcast(Intent.ACTION_UID_REMOVED,
-                        removedPackage, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND,
+                        null, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND,
                     null, null, broadcastUsers, instantUserIds);
             }
         }
@@ -22312,10 +22284,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);
@@ -23124,6 +23105,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;
+    }
+}