Merge "Adb command for binary push logging" into qt-dev
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index a9f5208e..ec02b12 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -68,12 +68,8 @@
 const int FIELD_ID_DUMP_REPORT_REASON = 8;
 const int FIELD_ID_STRINGS = 9;
 
-const int FIELD_ID_ACTIVE_CONFIG_LIST = 1;
-const int FIELD_ID_CONFIG_ID = 1;
-const int FIELD_ID_CONFIG_UID = 2;
-const int FIELD_ID_ACTIVE_METRIC = 3;
-const int FIELD_ID_METRIC_ID = 1;
-const int FIELD_ID_TIME_TO_LIVE_NANOS = 2;
+// for ActiveConfigList
+const int FIELD_ID_ACTIVE_CONFIG_LIST_CONFIG = 1;
 
 #define NS_PER_HOUR 3600 * NS_PER_SEC
 
@@ -523,7 +519,7 @@
     std::lock_guard<std::mutex> lock(mMetricsMutex);
     auto it = mMetricsManagers.find(key);
     if (it != mMetricsManagers.end()) {
-        WriteDataToDiskLocked(key, getElapsedRealtimeNs(), CONFIG_REMOVED, 
+        WriteDataToDiskLocked(key, getElapsedRealtimeNs(), CONFIG_REMOVED,
                               NO_TIME_CONSTRAINTS);
         mMetricsManagers.erase(it);
         mUidMap->OnConfigRemoved(key);
@@ -613,7 +609,7 @@
     mOnDiskDataConfigs.insert(key);
 }
 
-void StatsLogProcessor::WriteMetricsActivationToDisk(int64_t currentTimeNs) {
+void StatsLogProcessor::SaveActiveConfigsToDisk(int64_t currentTimeNs) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
 
     const int64_t timeNs = getElapsedRealtimeNs();
@@ -629,28 +625,12 @@
     mLastActiveMetricsWriteNs = timeNs;
 
     ProtoOutputStream proto;
-
     for (const auto& pair : mMetricsManagers) {
-        uint64_t activeConfigListToken = proto.start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
-                                                     FIELD_ID_ACTIVE_CONFIG_LIST);
-        proto.write(FIELD_TYPE_INT64 | FIELD_ID_CONFIG_ID, (long long)pair.first.GetId());
-        proto.write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_UID, pair.first.GetUid());
-
-        vector<MetricProducer*> activeMetrics;
-        pair.second->prepForShutDown(currentTimeNs);
-        pair.second->getActiveMetrics(activeMetrics);
-        for (MetricProducer* metric : activeMetrics) {
-            if (metric->isActive()) {
-                uint64_t metricToken = proto.start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
-                                                   FIELD_ID_ACTIVE_METRIC);
-                proto.write(FIELD_TYPE_INT64 | FIELD_ID_METRIC_ID,
-                            (long long)metric->getMetricId());
-                proto.write(FIELD_TYPE_INT64 | FIELD_ID_TIME_TO_LIVE_NANOS,
-                            (long long)metric->getRemainingTtlNs(currentTimeNs));
-                proto.end(metricToken);
-            }
-        }
-        proto.end(activeConfigListToken);
+        const sp<MetricsManager>& metricsManager = pair.second;
+        uint64_t configToken = proto.start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
+                                                     FIELD_ID_ACTIVE_CONFIG_LIST_CONFIG);
+        metricsManager->writeActiveConfigToProtoOutputStream(currentTimeNs, &proto);
+        proto.end(configToken);
     }
 
     string file_name = StringPrintf("%s/active_metrics", STATS_ACTIVE_METRIC_DIR);
@@ -664,30 +644,45 @@
     proto.flush(fd.get());
 }
 
-void StatsLogProcessor::LoadMetricsActivationFromDisk() {
+void StatsLogProcessor::LoadActiveConfigsFromDisk() {
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
+
     string file_name = StringPrintf("%s/active_metrics", STATS_ACTIVE_METRIC_DIR);
     int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC);
-    if (fd != -1) {
-        string content;
-        if (android::base::ReadFdToString(fd, &content)) {
-            ActiveConfigList activeConfigList;
-            if (activeConfigList.ParseFromString(content)) {
-                for (int i = 0; i < activeConfigList.active_config_size(); i++) {
-                    const auto& config = activeConfigList.active_config(i);
-                    ConfigKey key(config.uid(), config.config_id());
-                    auto it = mMetricsManagers.find(key);
-                    if (it == mMetricsManagers.end()) {
-                        ALOGE("No config found for config %s", key.ToString().c_str());
-                        continue;
-                    }
-                    VLOG("Setting active config %s", key.ToString().c_str());
-                    it->second->setActiveMetrics(config, mTimeBaseNs);
-                }
-            }
-            VLOG("Successfully loaded %d active configs.", activeConfigList.active_config_size());
-        }
-        close(fd);
+    if (-1 == fd) {
+        VLOG("Attempt to read %s but failed", file_name.c_str());
+        StorageManager::deleteFile(file_name.c_str());
+        return;
     }
+    string content;
+    if (!android::base::ReadFdToString(fd, &content)) {
+        ALOGE("Attempt to read %s but failed", file_name.c_str());
+        close(fd);
+        StorageManager::deleteFile(file_name.c_str());
+        return;
+    }
+
+    close(fd);
+
+    ActiveConfigList activeConfigList;
+    if (!activeConfigList.ParseFromString(content)) {
+        ALOGE("Attempt to read %s but failed; failed to load active configs", file_name.c_str());
+        StorageManager::deleteFile(file_name.c_str());
+        return;
+    }
+    for (int i = 0; i < activeConfigList.config_size(); i++) {
+        const auto& config = activeConfigList.config(i);
+        ConfigKey key(config.uid(), config.id());
+        auto it = mMetricsManagers.find(key);
+        if (it == mMetricsManagers.end()) {
+            ALOGE("No config found for config %s", key.ToString().c_str());
+            continue;
+        }
+        VLOG("Setting active config %s", key.ToString().c_str());
+        it->second->loadActiveConfig(config, mTimeBaseNs);
+    }
+    VLOG("Successfully loaded %d active configs.", activeConfigList.config_size());
+
     StorageManager::deleteFile(file_name.c_str());
 }
 
@@ -709,7 +704,7 @@
     }
 }
 
-void StatsLogProcessor::WriteDataToDisk(const DumpReportReason dumpReportReason, 
+void StatsLogProcessor::WriteDataToDisk(const DumpReportReason dumpReportReason,
                                         const DumpLatency dumpLatency) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
     WriteDataToDiskLocked(dumpReportReason, dumpLatency);
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 6178a4b..0dc597b 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -89,11 +89,11 @@
     void WriteDataToDisk(const DumpReportReason dumpReportReason,
                          const DumpLatency dumpLatency);
 
-    /* Persist metric activation status onto disk. */
-    void WriteMetricsActivationToDisk(int64_t currentTimeNs);
+    /* Persist configs containing metrics with active activations to disk. */
+    void SaveActiveConfigsToDisk(int64_t currentTimeNs);
 
-    /* Load metric activation status from disk. */
-    void LoadMetricsActivationFromDisk();
+    /* Load configs containing metrics with active activations from disk. */
+    void LoadActiveConfigsFromDisk();
 
     // Reset all configs.
     void resetConfigs();
@@ -221,6 +221,9 @@
     FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge);
     FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead);
     FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBoot);
+    FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations);
+    FRIEND_TEST(StatsLogProcessorTest,
+            TestActivationOnBootMultipleActivationsDifferentActivationTypes);
 
     FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration1);
     FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration2);
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 4063c85..623a1f2 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -1038,7 +1038,7 @@
     ENFORCE_UID(AID_SYSTEM);
     VLOG("StatsService::informDeviceShutdown");
     mProcessor->WriteDataToDisk(DEVICE_SHUTDOWN, FAST);
-    mProcessor->WriteMetricsActivationToDisk(getElapsedRealtimeNs());
+    mProcessor->SaveActiveConfigsToDisk(getElapsedRealtimeNs());
     return Status::ok();
 }
 
@@ -1073,14 +1073,14 @@
 
 void StatsService::Startup() {
     mConfigManager->Startup();
-    mProcessor->LoadMetricsActivationFromDisk();
+    mProcessor->LoadActiveConfigsFromDisk();
 }
 
 void StatsService::Terminate() {
     ALOGI("StatsService::Terminating");
     if (mProcessor != nullptr) {
         mProcessor->WriteDataToDisk(TERMINATION_SIGNAL_RECEIVED, FAST);
-        mProcessor->WriteMetricsActivationToDisk(getElapsedRealtimeNs());
+        mProcessor->SaveActiveConfigsToDisk(getElapsedRealtimeNs());
     }
 }
 
diff --git a/cmds/statsd/src/active_config_list.proto b/cmds/statsd/src/active_config_list.proto
index 0e9ee03..ef8e50b 100644
--- a/cmds/statsd/src/active_config_list.proto
+++ b/cmds/statsd/src/active_config_list.proto
@@ -21,23 +21,26 @@
 option java_multiple_files = true;
 option java_outer_classname = "ActiveConfigProto";
 
+message ActiveEventActivation {
+    optional int32 atom_matcher_index = 1;
+
+    // Time left in activation. When this proto is loaded after device boot,
+    // the activation should be set to active for this duration.
+    optional int64 remaining_ttl_nanos = 2;
+}
+
 message ActiveMetric {
-    // metric id
-    optional int64 metric_id = 1;
-    // Remaining time to live in nano seconds. -1 for infinity.
-    optional int64 time_to_live_nanos = 2;
+    optional int64 id = 1;
+    repeated ActiveEventActivation activation = 2;
 }
 
 message ActiveConfig {
-    // config id
-    optional int64 config_id = 1;
-    // config uid
+    optional int64 id = 1;
     optional int32 uid = 2;
-    // metrics
-    repeated ActiveMetric active_metric = 3;
+    repeated ActiveMetric metric = 3;
 }
 
 // all configs and their metrics on device.
 message ActiveConfigList {
-    repeated ActiveConfig active_config = 1;
-}
\ No newline at end of file
+    repeated ActiveConfig config = 1;
+}
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index e22b853..9ad7f09 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -18,12 +18,26 @@
 #include "Log.h"
 #include "MetricProducer.h"
 
+using android::util::FIELD_COUNT_REPEATED;
+using android::util::FIELD_TYPE_INT32;
+using android::util::FIELD_TYPE_INT64;
+using android::util::FIELD_TYPE_MESSAGE;
+using android::util::ProtoOutputStream;
+
 namespace android {
 namespace os {
 namespace statsd {
 
 using std::map;
 
+// for ActiveMetric
+const int FIELD_ID_ACTIVE_METRIC_ID = 1;
+const int FIELD_ID_ACTIVE_METRIC_ACTIVATION = 2;
+
+// for ActiveEventActivation
+const int FIELD_ID_ACTIVE_EVENT_ACTIVATION_ATOM_MATCHER_INDEX = 1;
+const int FIELD_ID_ACTIVE_EVENT_ACTIVATION_REMAINING_TTL_NANOS = 2;
+
 void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) {
     if (!mIsActive) {
         return;
@@ -74,7 +88,7 @@
     bool isActive = mEventActivationMap.empty();
     for (auto& it : mEventActivationMap) {
         if (it.second->state == ActivationState::kActive &&
-            elapsedTimestampNs > it.second->ttl_ns + it.second->activation_ns) {
+            elapsedTimestampNs > it.second->ttl_ns + it.second->start_ns) {
             it.second->state = ActivationState::kNotActive;
         }
         if (it.second->state == ActivationState::kActive) {
@@ -95,8 +109,8 @@
     }
 }
 
-void MetricProducer::addActivation(int activationTrackerIndex, int64_t ttl_seconds,
-                                   int deactivationTrackerIndex) {
+void MetricProducer::addActivation(int activationTrackerIndex, const ActivationType& activationType,
+        int64_t ttl_seconds, int deactivationTrackerIndex) {
     std::lock_guard<std::mutex> lock(mMutex);
     // When a metric producer does not depend on any activation, its mIsActive is true.
     // Therefore, if this is the 1st activation, mIsActive will turn to false. Otherwise it does not
@@ -104,8 +118,8 @@
     if  (mEventActivationMap.empty()) {
         mIsActive = false;
     }
-    std::shared_ptr<Activation> activation = std::make_shared<Activation>();
-    activation->ttl_ns = ttl_seconds * NS_PER_SEC;
+    std::shared_ptr<Activation> activation =
+            std::make_shared<Activation>(activationType, ttl_seconds * NS_PER_SEC);
     mEventActivationMap.emplace(activationTrackerIndex, activation);
     if (-1 != deactivationTrackerIndex) {
         mEventDeactivationMap.emplace(deactivationTrackerIndex, activation);
@@ -117,13 +131,16 @@
     if (it == mEventActivationMap.end()) {
         return;
     }
-    if (mActivationType == MetricActivation::ACTIVATE_ON_BOOT &&
-        it->second->state == ActivationState::kNotActive) {
-        it->second->state = ActivationState::kActiveOnBoot;
+    auto& activation = it->second;
+    if (ACTIVATE_ON_BOOT == activation->activationType) {
+        if (ActivationState::kNotActive == activation->state) {
+            activation->state = ActivationState::kActiveOnBoot;
+        }
+        // If the Activation is already active or set to kActiveOnBoot, do nothing.
         return;
     }
-    it->second->activation_ns = elapsedTimestampNs;
-    it->second->state = ActivationState::kActive;
+    activation->start_ns = elapsedTimestampNs;
+    activation->state = ActivationState::kActive;
     mIsActive = true;
 }
 
@@ -135,46 +152,55 @@
     it->second->state = ActivationState::kNotActive;
 }
 
-void MetricProducer::setActiveLocked(int64_t currentTimeNs, int64_t remainingTtlNs) {
+void MetricProducer::loadActiveMetricLocked(const ActiveMetric& activeMetric,
+                                            int64_t currentTimeNs) {
     if (mEventActivationMap.size() == 0) {
         return;
     }
-    for (auto& pair : mEventActivationMap) {
-        auto& activation = pair.second;
-        if (activation->ttl_ns >= remainingTtlNs) {
-            activation->activation_ns = currentTimeNs + remainingTtlNs - activation->ttl_ns;
-            activation->state = kActive;
-            mIsActive = true;
-            VLOG("setting new activation->time to %lld, %lld, %lld",
-                 (long long)activation->activation_ns, (long long)currentTimeNs,
-                 (long long)remainingTtlNs);
-            return;
+    for (int i = 0; i < activeMetric.activation_size(); i++) {
+        const auto& activeEventActivation = activeMetric.activation(i);
+        auto it = mEventActivationMap.find(activeEventActivation.atom_matcher_index());
+        if (it == mEventActivationMap.end()) {
+            ALOGE("Saved event activation not found");
+            continue;
         }
+        auto& activation = it->second;
+        // We don't want to change the ttl for future activations, so we set the start_ns
+        // such that start_ns + ttl_ns == currentTimeNs + remaining_ttl_nanos
+        activation->start_ns =
+            currentTimeNs + activeEventActivation.remaining_ttl_nanos() - activation->ttl_ns;
+        activation->state = ActivationState::kActive;
+        mIsActive = true;
     }
-    ALOGE("Required ttl is longer than all possible activations.");
 }
 
-int64_t MetricProducer::getRemainingTtlNsLocked(int64_t currentTimeNs) const {
-    int64_t maxTtl = 0;
-    for (const auto& activation : mEventActivationMap) {
-        if (activation.second->state == kActive) {
-            maxTtl = std::max(maxTtl, activation.second->ttl_ns + activation.second->activation_ns -
-                                              currentTimeNs);
-        }
-    }
-    return maxTtl;
-}
+void MetricProducer::writeActiveMetricToProtoOutputStream(
+        int64_t currentTimeNs, ProtoOutputStream* proto) {
+    proto->write(FIELD_TYPE_INT64 | FIELD_ID_ACTIVE_METRIC_ID, (long long)mMetricId);
+    for (auto& it : mEventActivationMap) {
+        const int atom_matcher_index = it.first;
+        const std::shared_ptr<Activation>& activation = it.second;
 
-void MetricProducer::prepActiveForBootIfNecessaryLocked(int64_t currentTimeNs) {
-    if (mActivationType != MetricActivation::ACTIVATE_ON_BOOT) {
-        return;
-    }
-    for (auto& activation : mEventActivationMap) {
-        if (activation.second->state == kActiveOnBoot) {
-            activation.second->state = kActive;
-            activation.second->activation_ns = currentTimeNs;
-            mIsActive = true;
+        if (ActivationState::kNotActive == activation->state ||
+                (ActivationState::kActive == activation->state &&
+                 activation->start_ns + activation->ttl_ns < currentTimeNs)) {
+            continue;
         }
+
+        const uint64_t activationToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
+                FIELD_ID_ACTIVE_METRIC_ACTIVATION);
+        proto->write(FIELD_TYPE_INT32 | FIELD_ID_ACTIVE_EVENT_ACTIVATION_ATOM_MATCHER_INDEX,
+                atom_matcher_index);
+        if (ActivationState::kActive == activation->state) {
+            const int64_t remainingTtlNs =
+                    activation->start_ns + activation->ttl_ns - currentTimeNs;
+            proto->write(FIELD_TYPE_INT64 | FIELD_ID_ACTIVE_EVENT_ACTIVATION_REMAINING_TTL_NANOS,
+                    (long long)remainingTtlNs);
+        } else if (ActivationState::kActiveOnBoot == activation->state) {
+            proto->write(FIELD_TYPE_INT64 | FIELD_ID_ACTIVE_EVENT_ACTIVATION_REMAINING_TTL_NANOS,
+                    (long long)activation->ttl_ns);
+        }
+        proto->end(activationToken);
     }
 }
 
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 750566d..7676f59 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -19,6 +19,7 @@
 
 #include <shared_mutex>
 
+#include <frameworks/base/cmds/statsd/src/active_config_list.pb.h>
 #include "HashableDimensionKey.h"
 #include "anomaly/AnomalyTracker.h"
 #include "condition/ConditionWizard.h"
@@ -198,15 +199,9 @@
         return mMetricId;
     }
 
-    int64_t getRemainingTtlNs(int64_t currentTimeNs) const {
+    void loadActiveMetric(const ActiveMetric& activeMetric, int64_t currentTimeNs) {
         std::lock_guard<std::mutex> lock(mMutex);
-        return getRemainingTtlNsLocked(currentTimeNs);
-    }
-
-    // Set metric to active for ttlNs.
-    void setActive(int64_t currentTimeNs, int64_t remainingTtlNs) {
-        std::lock_guard<std::mutex> lock(mMutex);
-        setActiveLocked(currentTimeNs, remainingTtlNs);
+        loadActiveMetricLocked(activeMetric, currentTimeNs);
     }
 
     // Let MetricProducer drop in-memory data to save memory.
@@ -238,17 +233,8 @@
         return isActiveLocked();
     }
 
-    void prepActiveForBootIfNecessary(int64_t currentTimeNs) {
-        std::lock_guard<std::mutex> lock(mMutex);
-        prepActiveForBootIfNecessaryLocked(currentTimeNs);
-    }
-
-    void addActivation(int activationTrackerIndex, int64_t ttl_seconds,
-                       int deactivationTrackerIndex = -1);
-
-    inline void setActivationType(const MetricActivation::ActivationType& activationType) {
-        mActivationType = activationType;
-    }
+    void addActivation(int activationTrackerIndex, const ActivationType& activationType,
+            int64_t ttl_seconds, int deactivationTrackerIndex = -1);
 
     void prepareFistBucket() {
         std::lock_guard<std::mutex> lock(mMutex);
@@ -257,6 +243,8 @@
 
     void flushIfExpire(int64_t elapsedTimestampNs);
 
+    void writeActiveMetricToProtoOutputStream(
+            int64_t currentTimeNs, ProtoOutputStream* proto);
 protected:
     virtual void onConditionChangedLocked(const bool condition, const int64_t eventTime) = 0;
     virtual void onSlicedConditionMayChangeLocked(bool overallCondition,
@@ -282,9 +270,7 @@
 
     void prepActiveForBootIfNecessaryLocked(int64_t currentTimeNs);
 
-    int64_t getRemainingTtlNsLocked(int64_t currentTimeNs) const;
-
-    void setActiveLocked(int64_t currentTimeNs, int64_t remainingTtlNs);
+    void loadActiveMetricLocked(const ActiveMetric& activeMetric, int64_t currentTimeNs);
 
     virtual void prepareFistBucketLocked() {};
     /**
@@ -396,11 +382,16 @@
     mutable std::mutex mMutex;
 
     struct Activation {
-        Activation() : ttl_ns(0), activation_ns(0), state(ActivationState::kNotActive)  {}
+        Activation(const ActivationType& activationType, const int64_t ttlNs)
+            : ttl_ns(ttlNs),
+              start_ns(0),
+              state(ActivationState::kNotActive),
+              activationType(activationType) {}
 
-        int64_t ttl_ns;
-        int64_t activation_ns;
+        const int64_t ttl_ns;
+        int64_t start_ns;
         ActivationState state;
+        const ActivationType activationType;
     };
     // When the metric producer has multiple activations, these activations are ORed to determine
     // whether the metric producer is ready to generate metrics.
@@ -411,8 +402,6 @@
 
     bool mIsActive;
 
-    MetricActivation::ActivationType mActivationType;
-
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetric);
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation);
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations);
@@ -420,6 +409,9 @@
 
     FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead);
     FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBoot);
+    FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations);
+    FRIEND_TEST(StatsLogProcessorTest,
+            TestActivationOnBootMultipleActivationsDifferentActivationTypes);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 6a55289..947f377 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -54,6 +54,11 @@
 const int FIELD_ID_ANNOTATIONS_INT64 = 1;
 const int FIELD_ID_ANNOTATIONS_INT32 = 2;
 
+// for ActiveConfig
+const int FIELD_ID_ACTIVE_CONFIG_ID = 1;
+const int FIELD_ID_ACTIVE_CONFIG_UID = 2;
+const int FIELD_ID_ACTIVE_CONFIG_METRIC = 3;
+
 MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config,
                                const int64_t timeBaseNs, const int64_t currentTimeNs,
                                const sp<UidMap>& uidMap,
@@ -503,25 +508,41 @@
     return totalSize;
 }
 
-void MetricsManager::setActiveMetrics(ActiveConfig config, int64_t currentTimeNs) {
-    if (config.active_metric_size() == 0) {
+void MetricsManager::loadActiveConfig(const ActiveConfig& config, int64_t currentTimeNs) {
+    if (config.metric_size() == 0) {
         ALOGW("No active metric for config %s", mConfigKey.ToString().c_str());
         return;
     }
 
-    for (int i = 0; i < config.active_metric_size(); i++) {
-        for (int metric : mMetricIndexesWithActivation) {
-            if (mAllMetricProducers[metric]->getMetricId() == config.active_metric(i).metric_id()) {
-                VLOG("Setting active metric: %lld",
-                     (long long)mAllMetricProducers[metric]->getMetricId());
-                mAllMetricProducers[metric]->setActive(
-                        currentTimeNs, config.active_metric(i).time_to_live_nanos());
-                mIsActive = true;
+    for (int i = 0; i < config.metric_size(); i++) {
+        const auto& activeMetric = config.metric(i);
+        for (int metricIndex : mMetricIndexesWithActivation) {
+            const auto& metric = mAllMetricProducers[metricIndex];
+            if (metric->getMetricId() == activeMetric.id()) {
+                VLOG("Setting active metric: %lld", (long long)metric->getMetricId());
+                metric->loadActiveMetric(activeMetric, currentTimeNs);
+                mIsActive |= metric->isActive();
             }
         }
     }
 }
 
+void MetricsManager::writeActiveConfigToProtoOutputStream(
+        int64_t currentTimeNs, ProtoOutputStream* proto) {
+    proto->write(FIELD_TYPE_INT64 | FIELD_ID_ACTIVE_CONFIG_ID, (long long)mConfigKey.GetId());
+    proto->write(FIELD_TYPE_INT32 | FIELD_ID_ACTIVE_CONFIG_UID, mConfigKey.GetUid());
+    for (int metricIndex : mMetricIndexesWithActivation) {
+        const auto& metric = mAllMetricProducers[metricIndex];
+        const uint64_t metricToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
+                FIELD_ID_ACTIVE_CONFIG_METRIC);
+        metric->writeActiveMetricToProtoOutputStream(currentTimeNs, proto);
+        proto->end(metricToken);
+    }
+}
+
+
+
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index d05bb8b..818131e 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -16,7 +16,6 @@
 
 #pragma once
 
-#include <frameworks/base/cmds/statsd/src/active_config_list.pb.h>
 #include "anomaly/AlarmMonitor.h"
 #include "anomaly/AlarmTracker.h"
 #include "anomaly/AnomalyTracker.h"
@@ -139,21 +138,10 @@
         return mIsActive;
     }
 
-    inline void getActiveMetrics(std::vector<MetricProducer*>& metrics) const {
-        for (const auto& metric : mAllMetricProducers) {
-            if (metric->isActive()) {
-                metrics.push_back(metric.get());
-            }
-        }
-    }
+    void loadActiveConfig(const ActiveConfig& config, int64_t currentTimeNs);
 
-    inline void prepForShutDown(int64_t currentTimeNs) {
-        for (const auto& metric : mAllMetricProducers) {
-            metric->prepActiveForBootIfNecessary(currentTimeNs);
-        }
-    }
-
-    void setActiveMetrics(ActiveConfig config, int64_t currentTimeNs);
+    void writeActiveConfigToProtoOutputStream(
+            int64_t currentTimeNs, ProtoOutputStream* proto);
 
 private:
     // For test only.
@@ -299,6 +287,9 @@
 
     FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead);
     FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBoot);
+    FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations);
+    FRIEND_TEST(StatsLogProcessorTest,
+            TestActivationOnBootMultipleActivationsDifferentActivationTypes);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 31b424e..b027fa0 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -727,7 +727,6 @@
             return false;
         }
         const sp<MetricProducer>& metric = allMetricProducers[metricTrackerIndex];
-        metric->setActivationType(metric_activation.activation_type());
         metricsWithActivation.push_back(metricTrackerIndex);
         for (int j = 0; j < metric_activation.event_activation_size(); ++j) {
             const EventActivation& activation = metric_activation.event_activation(j);
@@ -740,6 +739,13 @@
             activationAtomTrackerToMetricMap[atomMatcherIndex].push_back(
                 metricTrackerIndex);
 
+            ActivationType activationType;
+            if (activation.has_activation_type()) {
+                activationType = activation.activation_type();
+            } else {
+                activationType = metric_activation.activation_type();
+            }
+
             if (activation.has_deactivation_atom_matcher_id()) {
                 auto deactivationAtomMatcherIt =
                         logEventTrackerMap.find(activation.deactivation_atom_matcher_id());
@@ -750,10 +756,10 @@
                 const int deactivationMatcherIndex = deactivationAtomMatcherIt->second;
                 deactivationAtomTrackerToMetricMap[deactivationMatcherIndex]
                         .push_back(metricTrackerIndex);
-                metric->addActivation(atomMatcherIndex, activation.ttl_seconds(),
+                metric->addActivation(atomMatcherIndex, activationType, activation.ttl_seconds(),
                                       deactivationMatcherIndex);
             } else {
-                metric->addActivation(atomMatcherIndex, activation.ttl_seconds());
+                metric->addActivation(atomMatcherIndex, activationType, activation.ttl_seconds());
             }
         }
     }
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 2260b9b..4e419b6 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -377,21 +377,23 @@
   optional float probability_of_informing = 7 [default = 1.1];
 }
 
+enum ActivationType {
+  ACTIVATION_TYPE_UNKNOWN = 0;
+  ACTIVATE_IMMEDIATELY = 1;
+  ACTIVATE_ON_BOOT = 2;
+}
+
 message EventActivation {
   optional int64 atom_matcher_id = 1;
   optional int64 ttl_seconds = 2;
   optional int64 deactivation_atom_matcher_id = 3;
+  optional ActivationType activation_type = 4;
 }
 
 message MetricActivation {
   optional int64 metric_id = 1;
 
-  enum ActivationType {
-      UNKNOWN = 0;
-      ACTIVATE_IMMEDIATELY = 1;
-      ACTIVATE_ON_BOOT = 2;
-  }
-  optional ActivationType activation_type = 3;
+  optional ActivationType activation_type = 3 [deprecated = true];
 
   repeated EventActivation event_activation = 2;
 }
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index 91e282a..49b4e90 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -339,6 +339,7 @@
 
     auto metric3Activation = config2.add_metric_activation();
     metric3Activation->set_metric_id(metricId3);
+    metric3Activation->set_activation_type(ACTIVATE_IMMEDIATELY);
     auto metric3ActivationTrigger = metric3Activation->add_event_activation();
     metric3ActivationTrigger->set_atom_matcher_id(wakelockAcquireMatcher.id());
     metric3ActivationTrigger->set_ttl_seconds(100);
@@ -366,12 +367,14 @@
 
     auto metric5Activation = config3.add_metric_activation();
     metric5Activation->set_metric_id(metricId5);
+    metric5Activation->set_activation_type(ACTIVATE_IMMEDIATELY);
     auto metric5ActivationTrigger = metric5Activation->add_event_activation();
     metric5ActivationTrigger->set_atom_matcher_id(wakelockAcquireMatcher.id());
     metric5ActivationTrigger->set_ttl_seconds(100);
 
     auto metric6Activation = config3.add_metric_activation();
     metric6Activation->set_metric_id(metricId6);
+    metric6Activation->set_activation_type(ACTIVATE_IMMEDIATELY);
     auto metric6ActivationTrigger = metric6Activation->add_event_activation();
     metric6ActivationTrigger->set_atom_matcher_id(wakelockAcquireMatcher.id());
     metric6ActivationTrigger->set_ttl_seconds(200);
@@ -507,17 +510,13 @@
 
     // When we shut down, metrics 3 & 5 have 100ns remaining, metric 6 has 100s + 100ns.
     int64_t shutDownTime = timeBase1 + 100 * NS_PER_SEC;
-    EXPECT_TRUE(metricProducer3->isActive());
-    int64_t ttl3 = metricProducer3->getRemainingTtlNs(shutDownTime);
-    EXPECT_EQ(100, ttl3);
-    EXPECT_TRUE(metricProducer5->isActive());
-    int64_t ttl5 = metricProducer5->getRemainingTtlNs(shutDownTime);
-    EXPECT_EQ(100, ttl5);
-    EXPECT_TRUE(metricProducer6->isActive());
-    int64_t ttl6 = metricProducer6->getRemainingTtlNs(shutDownTime);
-    EXPECT_EQ(100 + 100 * NS_PER_SEC, ttl6);
-
-    processor.WriteMetricsActivationToDisk(shutDownTime);
+    processor.SaveActiveConfigsToDisk(shutDownTime);
+    const int64_t ttl3 = event->GetElapsedTimestampNs() +
+            metric3ActivationTrigger->ttl_seconds() * NS_PER_SEC - shutDownTime;
+    const int64_t ttl5 = event->GetElapsedTimestampNs() +
+            metric5ActivationTrigger->ttl_seconds() * NS_PER_SEC - shutDownTime;
+    const int64_t ttl6 = event->GetElapsedTimestampNs() +
+            metric6ActivationTrigger->ttl_seconds() * NS_PER_SEC - shutDownTime;
 
     // Create a second StatsLogProcessor and push the same 3 configs.
     long timeBase2 = 1000;
@@ -611,25 +610,25 @@
     EXPECT_FALSE(metricProducer1003->isActive());
     const auto& activation1003 = metricProducer1003->mEventActivationMap.begin()->second;
     EXPECT_EQ(100 * NS_PER_SEC, activation1003->ttl_ns);
-    EXPECT_EQ(0, activation1003->activation_ns);
+    EXPECT_EQ(0, activation1003->start_ns);
     EXPECT_FALSE(metricProducer1005->isActive());
     const auto& activation1005 = metricProducer1005->mEventActivationMap.begin()->second;
     EXPECT_EQ(100 * NS_PER_SEC, activation1005->ttl_ns);
-    EXPECT_EQ(0, activation1005->activation_ns);
+    EXPECT_EQ(0, activation1005->start_ns);
     EXPECT_FALSE(metricProducer1006->isActive());
     const auto& activation1006 = metricProducer1006->mEventActivationMap.begin()->second;
     EXPECT_EQ(200 * NS_PER_SEC, activation1006->ttl_ns);
-    EXPECT_EQ(0, activation1006->activation_ns);
+    EXPECT_EQ(0, activation1006->start_ns);
 
-    processor2->LoadMetricsActivationFromDisk();
+    processor2->LoadActiveConfigsFromDisk();
 
     // After loading activations from disk, assert that all 3 metrics are active.
     EXPECT_TRUE(metricProducer1003->isActive());
-    EXPECT_EQ(timeBase2 + ttl3 - activation1003->ttl_ns, activation1003->activation_ns);
+    EXPECT_EQ(timeBase2 + ttl3 - activation1003->ttl_ns, activation1003->start_ns);
     EXPECT_TRUE(metricProducer1005->isActive());
-    EXPECT_EQ(timeBase2 + ttl5 - activation1005->ttl_ns, activation1005->activation_ns);
+    EXPECT_EQ(timeBase2 + ttl5 - activation1005->ttl_ns, activation1005->start_ns);
     EXPECT_TRUE(metricProducer1006->isActive());
-    EXPECT_EQ(timeBase2 + ttl6 - activation1006->ttl_ns, activation1003->activation_ns);
+    EXPECT_EQ(timeBase2 + ttl6 - activation1006->ttl_ns, activation1003->start_ns);
 
     // Make sure no more broadcasts have happened.
     EXPECT_EQ(broadcastCount, 1);
@@ -638,7 +637,6 @@
 TEST(StatsLogProcessorTest, TestActivationOnBoot) {
     int uid = 1111;
 
-    // Setup a simple config, no activation
     StatsdConfig config1;
     config1.set_id(12341);
     config1.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
@@ -659,7 +657,7 @@
 
     auto metric1Activation = config1.add_metric_activation();
     metric1Activation->set_metric_id(metricId1);
-    metric1Activation->set_activation_type(MetricActivation::ACTIVATE_ON_BOOT);
+    metric1Activation->set_activation_type(ACTIVATE_ON_BOOT);
     auto metric1ActivationTrigger = metric1Activation->add_event_activation();
     metric1ActivationTrigger->set_atom_matcher_id(wakelockAcquireMatcher.id());
     metric1ActivationTrigger->set_ttl_seconds(100);
@@ -697,7 +695,7 @@
 
     const auto& activation1 = metricProducer1->mEventActivationMap.begin()->second;
     EXPECT_EQ(100 * NS_PER_SEC, activation1->ttl_ns);
-    EXPECT_EQ(0, activation1->activation_ns);
+    EXPECT_EQ(0, activation1->start_ns);
     EXPECT_EQ(kNotActive, activation1->state);
 
     std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")};
@@ -705,15 +703,13 @@
     processor->OnLogEvent(event.get());
 
     EXPECT_FALSE(metricProducer1->isActive());
-    EXPECT_EQ(0, activation1->activation_ns);
+    EXPECT_EQ(0, activation1->start_ns);
     EXPECT_EQ(kActiveOnBoot, activation1->state);
 
     int64_t shutDownTime = timeBase1 + 100 * NS_PER_SEC;
-
-    processor->WriteMetricsActivationToDisk(shutDownTime);
-    EXPECT_TRUE(metricProducer1->isActive());
-    int64_t ttl1 = metricProducer1->getRemainingTtlNs(shutDownTime);
-    EXPECT_EQ(100 * NS_PER_SEC, ttl1);
+    processor->SaveActiveConfigsToDisk(shutDownTime);
+    EXPECT_FALSE(metricProducer1->isActive());
+    const int64_t ttl1 = metric1ActivationTrigger->ttl_seconds() * NS_PER_SEC;
 
     long timeBase2 = 1000;
     sp<StatsLogProcessor> processor2 =
@@ -747,13 +743,743 @@
 
     const auto& activation1001 = metricProducer1001->mEventActivationMap.begin()->second;
     EXPECT_EQ(100 * NS_PER_SEC, activation1001->ttl_ns);
-    EXPECT_EQ(0, activation1001->activation_ns);
+    EXPECT_EQ(0, activation1001->start_ns);
     EXPECT_EQ(kNotActive, activation1001->state);
 
-    processor2->LoadMetricsActivationFromDisk();
+    processor2->LoadActiveConfigsFromDisk();
 
     EXPECT_TRUE(metricProducer1001->isActive());
-    EXPECT_EQ(timeBase2 + ttl1 - activation1001->ttl_ns, activation1001->activation_ns);
+    EXPECT_EQ(timeBase2 + ttl1 - activation1001->ttl_ns, activation1001->start_ns);
+    EXPECT_EQ(kActive, activation1001->state);
+}
+
+TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations) {
+    int uid = 1111;
+
+    // Create config with 2 metrics:
+    // Metric 1: Activate on boot with 2 activations
+    // Metric 2: Always active
+    StatsdConfig config1;
+    config1.set_id(12341);
+    config1.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+    auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
+    auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
+    *config1.add_atom_matcher() = wakelockAcquireMatcher;
+    *config1.add_atom_matcher() = screenOnMatcher;
+
+    long metricId1 = 1234561;
+    long metricId2 = 1234562;
+
+    auto countMetric1 = config1.add_count_metric();
+    countMetric1->set_id(metricId1);
+    countMetric1->set_what(wakelockAcquireMatcher.id());
+    countMetric1->set_bucket(FIVE_MINUTES);
+
+    auto countMetric2 = config1.add_count_metric();
+    countMetric2->set_id(metricId2);
+    countMetric2->set_what(wakelockAcquireMatcher.id());
+    countMetric2->set_bucket(FIVE_MINUTES);
+
+    auto metric1Activation = config1.add_metric_activation();
+    metric1Activation->set_metric_id(metricId1);
+    metric1Activation->set_activation_type(ACTIVATE_ON_BOOT);
+    auto metric1ActivationTrigger1 = metric1Activation->add_event_activation();
+    metric1ActivationTrigger1->set_atom_matcher_id(wakelockAcquireMatcher.id());
+    metric1ActivationTrigger1->set_ttl_seconds(100);
+    auto metric1ActivationTrigger2 = metric1Activation->add_event_activation();
+    metric1ActivationTrigger2->set_atom_matcher_id(screenOnMatcher.id());
+    metric1ActivationTrigger2->set_ttl_seconds(200);
+
+    ConfigKey cfgKey1(uid, 12341);
+    long timeBase1 = 1;
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(timeBase1, timeBase1, config1, cfgKey1);
+
+    // Metric 1 is not active.
+    // Metric 2 is active.
+    // {{{---------------------------------------------------------------------------
+    EXPECT_EQ(1, processor->mMetricsManagers.size());
+    auto it = processor->mMetricsManagers.find(cfgKey1);
+    EXPECT_TRUE(it != processor->mMetricsManagers.end());
+    auto& metricsManager1 = it->second;
+    EXPECT_TRUE(metricsManager1->isActive());
+
+    auto metricIt = metricsManager1->mAllMetricProducers.begin();
+    for (; metricIt != metricsManager1->mAllMetricProducers.end(); metricIt++) {
+        if ((*metricIt)->getMetricId() == metricId1) {
+            break;
+        }
+    }
+    EXPECT_TRUE(metricIt != metricsManager1->mAllMetricProducers.end());
+    auto& metricProducer1 = *metricIt;
+    EXPECT_FALSE(metricProducer1->isActive());
+
+    metricIt = metricsManager1->mAllMetricProducers.begin();
+    for (; metricIt != metricsManager1->mAllMetricProducers.end(); metricIt++) {
+        if ((*metricIt)->getMetricId() == metricId2) {
+            break;
+        }
+    }
+    EXPECT_TRUE(metricIt != metricsManager1->mAllMetricProducers.end());
+    auto& metricProducer2 = *metricIt;
+    EXPECT_TRUE(metricProducer2->isActive());
+
+    int i = 0;
+    for (; i < metricsManager1->mAllAtomMatchers.size(); i++) {
+        if (metricsManager1->mAllAtomMatchers[i]->getId() ==
+                metric1ActivationTrigger1->atom_matcher_id()) {
+            break;
+        }
+    }
+    const auto& activation1 = metricProducer1->mEventActivationMap.at(i);
+    EXPECT_EQ(100 * NS_PER_SEC, activation1->ttl_ns);
+    EXPECT_EQ(0, activation1->start_ns);
+    EXPECT_EQ(kNotActive, activation1->state);
+
+    i = 0;
+    for (; i < metricsManager1->mAllAtomMatchers.size(); i++) {
+        if (metricsManager1->mAllAtomMatchers[i]->getId() ==
+                metric1ActivationTrigger2->atom_matcher_id()) {
+            break;
+        }
+    }
+    const auto& activation2 = metricProducer1->mEventActivationMap.at(i);
+    EXPECT_EQ(200 * NS_PER_SEC, activation2->ttl_ns);
+    EXPECT_EQ(0, activation2->start_ns);
+    EXPECT_EQ(kNotActive, activation2->state);
+    // }}}------------------------------------------------------------------------------
+
+    // Trigger Activation 1 for Metric 1
+    std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")};
+    auto event = CreateAcquireWakelockEvent(attributions1, "wl1", 100 + timeBase1);
+    processor->OnLogEvent(event.get());
+
+    // Metric 1 is not active; Activation 1 set to kActiveOnBoot
+    // Metric 2 is active.
+    // {{{---------------------------------------------------------------------------
+    EXPECT_FALSE(metricProducer1->isActive());
+    EXPECT_EQ(0, activation1->start_ns);
+    EXPECT_EQ(kActiveOnBoot, activation1->state);
+    EXPECT_EQ(0, activation2->start_ns);
+    EXPECT_EQ(kNotActive, activation2->state);
+
+    EXPECT_TRUE(metricProducer2->isActive());
+    // }}}-----------------------------------------------------------------------------
+
+    // Simulate shutdown by saving state to disk
+    int64_t shutDownTime = timeBase1 + 100 * NS_PER_SEC;
+    processor->SaveActiveConfigsToDisk(shutDownTime);
+    EXPECT_FALSE(metricProducer1->isActive());
+    int64_t ttl1 = metric1ActivationTrigger1->ttl_seconds() * NS_PER_SEC;
+
+    // Simulate device restarted state by creating new instance of StatsLogProcessor with the
+    // same config.
+    long timeBase2 = 1000;
+    sp<StatsLogProcessor> processor2 =
+            CreateStatsLogProcessor(timeBase2, timeBase2, config1, cfgKey1);
+
+    // Metric 1 is not active.
+    // Metric 2 is active.
+    // {{{---------------------------------------------------------------------------
+    EXPECT_EQ(1, processor2->mMetricsManagers.size());
+    it = processor2->mMetricsManagers.find(cfgKey1);
+    EXPECT_TRUE(it != processor2->mMetricsManagers.end());
+    auto& metricsManager1001 = it->second;
+    EXPECT_TRUE(metricsManager1001->isActive());
+
+    metricIt = metricsManager1001->mAllMetricProducers.begin();
+    for (; metricIt != metricsManager1001->mAllMetricProducers.end(); metricIt++) {
+        if ((*metricIt)->getMetricId() == metricId1) {
+            break;
+        }
+    }
+    EXPECT_TRUE(metricIt != metricsManager1001->mAllMetricProducers.end());
+    auto& metricProducer1001 = *metricIt;
+    EXPECT_FALSE(metricProducer1001->isActive());
+
+    metricIt = metricsManager1001->mAllMetricProducers.begin();
+    for (; metricIt != metricsManager1001->mAllMetricProducers.end(); metricIt++) {
+        if ((*metricIt)->getMetricId() == metricId2) {
+            break;
+        }
+    }
+    EXPECT_TRUE(metricIt != metricsManager1001->mAllMetricProducers.end());
+    auto& metricProducer1002 = *metricIt;
+    EXPECT_TRUE(metricProducer1002->isActive());
+
+    i = 0;
+    for (; i < metricsManager1001->mAllAtomMatchers.size(); i++) {
+        if (metricsManager1001->mAllAtomMatchers[i]->getId() ==
+                metric1ActivationTrigger1->atom_matcher_id()) {
+            break;
+        }
+    }
+    const auto& activation1001_1 = metricProducer1001->mEventActivationMap.at(i);
+    EXPECT_EQ(100 * NS_PER_SEC, activation1001_1->ttl_ns);
+    EXPECT_EQ(0, activation1001_1->start_ns);
+    EXPECT_EQ(kNotActive, activation1001_1->state);
+
+    i = 0;
+    for (; i < metricsManager1001->mAllAtomMatchers.size(); i++) {
+        if (metricsManager1001->mAllAtomMatchers[i]->getId() ==
+                metric1ActivationTrigger2->atom_matcher_id()) {
+            break;
+        }
+    }
+
+    const auto& activation1001_2 = metricProducer1001->mEventActivationMap.at(i);
+    EXPECT_EQ(200 * NS_PER_SEC, activation1001_2->ttl_ns);
+    EXPECT_EQ(0, activation1001_2->start_ns);
+    EXPECT_EQ(kNotActive, activation1001_2->state);
+    // }}}-----------------------------------------------------------------------------------
+
+    // Load saved state from disk.
+    processor2->LoadActiveConfigsFromDisk();
+
+    // Metric 1 active; Activation 1 is active, Activation 2 is not active
+    // Metric 2 is active.
+    // {{{---------------------------------------------------------------------------
+    EXPECT_TRUE(metricProducer1001->isActive());
+    EXPECT_EQ(timeBase2 + ttl1 - activation1001_1->ttl_ns, activation1001_1->start_ns);
+    EXPECT_EQ(kActive, activation1001_1->state);
+    EXPECT_EQ(0, activation1001_2->start_ns);
+    EXPECT_EQ(kNotActive, activation1001_2->state);
+
+    EXPECT_TRUE(metricProducer1002->isActive());
+    // }}}--------------------------------------------------------------------------------
+
+    // Trigger Activation 2 for Metric 1.
+    auto screenOnEvent = CreateScreenStateChangedEvent(
+            android::view::DISPLAY_STATE_ON,
+            timeBase2 + 200
+    );
+    processor2->OnLogEvent(screenOnEvent.get());
+
+    // Metric 1 active; Activation 1 is active, Activation 2 is set to kActiveOnBoot
+    // Metric 2 is active.
+    // {{{---------------------------------------------------------------------------
+    EXPECT_TRUE(metricProducer1001->isActive());
+    EXPECT_EQ(timeBase2 + ttl1 - activation1001_1->ttl_ns, activation1001_1->start_ns);
+    EXPECT_EQ(kActive, activation1001_1->state);
+    EXPECT_EQ(0, activation1001_2->start_ns);
+    EXPECT_EQ(kActiveOnBoot, activation1001_2->state);
+
+    EXPECT_TRUE(metricProducer1002->isActive());
+    // }}}---------------------------------------------------------------------------
+
+    // Simulate shutdown by saving state to disk
+    shutDownTime = timeBase2 + 50 * NS_PER_SEC;
+    processor2->SaveActiveConfigsToDisk(shutDownTime);
+    EXPECT_TRUE(metricProducer1001->isActive());
+    EXPECT_TRUE(metricProducer1002->isActive());
+    ttl1 = timeBase2 + metric1ActivationTrigger1->ttl_seconds() * NS_PER_SEC - shutDownTime;
+    int64_t ttl2 = metric1ActivationTrigger2->ttl_seconds() * NS_PER_SEC;
+
+    // Simulate device restarted state by creating new instance of StatsLogProcessor with the
+    // same config.
+    long timeBase3 = timeBase2 + 120 * NS_PER_SEC;
+    sp<StatsLogProcessor> processor3 =
+            CreateStatsLogProcessor(timeBase3, timeBase3, config1, cfgKey1);
+
+    // Metric 1 is not active.
+    // Metric 2 is active.
+    // {{{---------------------------------------------------------------------------
+    EXPECT_EQ(1, processor3->mMetricsManagers.size());
+    it = processor3->mMetricsManagers.find(cfgKey1);
+    EXPECT_TRUE(it != processor3->mMetricsManagers.end());
+    auto& metricsManagerTimeBase3 = it->second;
+    EXPECT_TRUE(metricsManagerTimeBase3->isActive());
+
+    metricIt = metricsManagerTimeBase3->mAllMetricProducers.begin();
+    for (; metricIt != metricsManagerTimeBase3->mAllMetricProducers.end(); metricIt++) {
+        if ((*metricIt)->getMetricId() == metricId1) {
+            break;
+        }
+    }
+    EXPECT_TRUE(metricIt != metricsManagerTimeBase3->mAllMetricProducers.end());
+    auto& metricProducerTimeBase3_1 = *metricIt;
+    EXPECT_FALSE(metricProducerTimeBase3_1->isActive());
+
+    metricIt = metricsManagerTimeBase3->mAllMetricProducers.begin();
+    for (; metricIt != metricsManagerTimeBase3->mAllMetricProducers.end(); metricIt++) {
+        if ((*metricIt)->getMetricId() == metricId2) {
+            break;
+        }
+    }
+    EXPECT_TRUE(metricIt != metricsManagerTimeBase3->mAllMetricProducers.end());
+    auto& metricProducerTimeBase3_2 = *metricIt;
+    EXPECT_TRUE(metricProducerTimeBase3_2->isActive());
+
+    i = 0;
+    for (; i < metricsManagerTimeBase3->mAllAtomMatchers.size(); i++) {
+        if (metricsManagerTimeBase3->mAllAtomMatchers[i]->getId() ==
+                metric1ActivationTrigger1->atom_matcher_id()) {
+            break;
+        }
+    }
+    const auto& activationTimeBase3_1 = metricProducerTimeBase3_1->mEventActivationMap.at(i);
+    EXPECT_EQ(100 * NS_PER_SEC, activationTimeBase3_1->ttl_ns);
+    EXPECT_EQ(0, activationTimeBase3_1->start_ns);
+    EXPECT_EQ(kNotActive, activationTimeBase3_1->state);
+
+    i = 0;
+    for (; i < metricsManagerTimeBase3->mAllAtomMatchers.size(); i++) {
+        if (metricsManagerTimeBase3->mAllAtomMatchers[i]->getId() ==
+                metric1ActivationTrigger2->atom_matcher_id()) {
+            break;
+        }
+    }
+
+    const auto& activationTimeBase3_2 = metricProducerTimeBase3_1->mEventActivationMap.at(i);
+    EXPECT_EQ(200 * NS_PER_SEC, activationTimeBase3_2->ttl_ns);
+    EXPECT_EQ(0, activationTimeBase3_2->start_ns);
+    EXPECT_EQ(kNotActive, activationTimeBase3_2->state);
+
+    EXPECT_TRUE(metricProducerTimeBase3_2->isActive());
+    // }}}----------------------------------------------------------------------------------
+
+    // Load saved state from disk.
+    processor3->LoadActiveConfigsFromDisk();
+
+    // Metric 1 active: Activation 1 is active, Activation 2 is active
+    // Metric 2 is active.
+    // {{{---------------------------------------------------------------------------
+    EXPECT_TRUE(metricProducerTimeBase3_1->isActive());
+    EXPECT_EQ(timeBase3 + ttl1 - activationTimeBase3_1->ttl_ns, activationTimeBase3_1->start_ns);
+    EXPECT_EQ(kActive, activationTimeBase3_1->state);
+    EXPECT_EQ(timeBase3 + ttl2 - activationTimeBase3_2->ttl_ns, activationTimeBase3_2->start_ns);
+    EXPECT_EQ(kActive, activationTimeBase3_2->state);
+
+    EXPECT_TRUE(metricProducerTimeBase3_2->isActive());
+    // }}}-------------------------------------------------------------------------------
+
+    // Trigger Activation 2 for Metric 1 again.
+    screenOnEvent = CreateScreenStateChangedEvent(
+            android::view::DISPLAY_STATE_ON,
+            timeBase3 + 100 * NS_PER_SEC
+    );
+    processor3->OnLogEvent(screenOnEvent.get());
+
+    // Metric 1 active; Activation 1 is not active, Activation 2 is set to active
+    // Metric 2 is active.
+    // {{{---------------------------------------------------------------------------
+    EXPECT_TRUE(metricProducerTimeBase3_1->isActive());
+    EXPECT_EQ(kNotActive, activationTimeBase3_1->state);
+    EXPECT_EQ(timeBase3 + ttl2 - activationTimeBase3_2->ttl_ns, activationTimeBase3_2->start_ns);
+    EXPECT_EQ(kActive, activationTimeBase3_2->state);
+
+    EXPECT_TRUE(metricProducerTimeBase3_2->isActive());
+    // }}}---------------------------------------------------------------------------
+
+    // Simulate shutdown by saving state to disk.
+    shutDownTime = timeBase3 + 500 * NS_PER_SEC;
+    processor3->SaveActiveConfigsToDisk(shutDownTime);
+    EXPECT_TRUE(metricProducer1001->isActive());
+    EXPECT_TRUE(metricProducer1002->isActive());
+    ttl1 = timeBase3 + ttl1 - shutDownTime;
+    ttl2 = timeBase3 + metric1ActivationTrigger2->ttl_seconds() * NS_PER_SEC - shutDownTime;
+
+    // Simulate device restarted state by creating new instance of StatsLogProcessor with the
+    // same config.
+    long timeBase4 = timeBase3 + 600 * NS_PER_SEC;
+    sp<StatsLogProcessor> processor4 =
+            CreateStatsLogProcessor(timeBase4, timeBase4, config1, cfgKey1);
+
+    // Metric 1 is not active.
+    // Metric 2 is active.
+    // {{{---------------------------------------------------------------------------
+    EXPECT_EQ(1, processor4->mMetricsManagers.size());
+    it = processor4->mMetricsManagers.find(cfgKey1);
+    EXPECT_TRUE(it != processor4->mMetricsManagers.end());
+    auto& metricsManagerTimeBase4 = it->second;
+    EXPECT_TRUE(metricsManagerTimeBase4->isActive());
+
+    metricIt = metricsManagerTimeBase4->mAllMetricProducers.begin();
+    for (; metricIt != metricsManagerTimeBase4->mAllMetricProducers.end(); metricIt++) {
+        if ((*metricIt)->getMetricId() == metricId1) {
+            break;
+        }
+    }
+    EXPECT_TRUE(metricIt != metricsManagerTimeBase4->mAllMetricProducers.end());
+    auto& metricProducerTimeBase4_1 = *metricIt;
+    EXPECT_FALSE(metricProducerTimeBase4_1->isActive());
+
+    metricIt = metricsManagerTimeBase4->mAllMetricProducers.begin();
+    for (; metricIt != metricsManagerTimeBase4->mAllMetricProducers.end(); metricIt++) {
+        if ((*metricIt)->getMetricId() == metricId2) {
+            break;
+        }
+    }
+    EXPECT_TRUE(metricIt != metricsManagerTimeBase4->mAllMetricProducers.end());
+    auto& metricProducerTimeBase4_2 = *metricIt;
+    EXPECT_TRUE(metricProducerTimeBase4_2->isActive());
+
+    i = 0;
+    for (; i < metricsManagerTimeBase4->mAllAtomMatchers.size(); i++) {
+        if (metricsManagerTimeBase4->mAllAtomMatchers[i]->getId() ==
+                metric1ActivationTrigger1->atom_matcher_id()) {
+            break;
+        }
+    }
+    const auto& activationTimeBase4_1 = metricProducerTimeBase4_1->mEventActivationMap.at(i);
+    EXPECT_EQ(100 * NS_PER_SEC, activationTimeBase4_1->ttl_ns);
+    EXPECT_EQ(0, activationTimeBase4_1->start_ns);
+    EXPECT_EQ(kNotActive, activationTimeBase4_1->state);
+
+    i = 0;
+    for (; i < metricsManagerTimeBase4->mAllAtomMatchers.size(); i++) {
+        if (metricsManagerTimeBase4->mAllAtomMatchers[i]->getId() ==
+                metric1ActivationTrigger2->atom_matcher_id()) {
+            break;
+        }
+    }
+
+    const auto& activationTimeBase4_2 = metricProducerTimeBase4_1->mEventActivationMap.at(i);
+    EXPECT_EQ(200 * NS_PER_SEC, activationTimeBase4_2->ttl_ns);
+    EXPECT_EQ(0, activationTimeBase4_2->start_ns);
+    EXPECT_EQ(kNotActive, activationTimeBase4_2->state);
+
+    EXPECT_TRUE(metricProducerTimeBase4_2->isActive());
+    // }}}----------------------------------------------------------------------------------
+
+    // Load saved state from disk.
+    processor4->LoadActiveConfigsFromDisk();
+
+    // Metric 1 active: Activation 1 is not active, Activation 2 is not active
+    // Metric 2 is active.
+    // {{{---------------------------------------------------------------------------
+    EXPECT_FALSE(metricProducerTimeBase4_1->isActive());
+    EXPECT_EQ(kNotActive, activationTimeBase4_1->state);
+    EXPECT_EQ(kNotActive, activationTimeBase4_2->state);
+
+    EXPECT_TRUE(metricProducerTimeBase4_2->isActive());
+    // }}}-------------------------------------------------------------------------------
+}
+
+TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivationsDifferentActivationTypes) {
+    int uid = 1111;
+
+    // Create config with 2 metrics:
+    // Metric 1: Activate on boot with 2 activations
+    // Metric 2: Always active
+    StatsdConfig config1;
+    config1.set_id(12341);
+    config1.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+    auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
+    auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
+    *config1.add_atom_matcher() = wakelockAcquireMatcher;
+    *config1.add_atom_matcher() = screenOnMatcher;
+
+    long metricId1 = 1234561;
+    long metricId2 = 1234562;
+
+    auto countMetric1 = config1.add_count_metric();
+    countMetric1->set_id(metricId1);
+    countMetric1->set_what(wakelockAcquireMatcher.id());
+    countMetric1->set_bucket(FIVE_MINUTES);
+
+    auto countMetric2 = config1.add_count_metric();
+    countMetric2->set_id(metricId2);
+    countMetric2->set_what(wakelockAcquireMatcher.id());
+    countMetric2->set_bucket(FIVE_MINUTES);
+
+    auto metric1Activation = config1.add_metric_activation();
+    metric1Activation->set_metric_id(metricId1);
+    metric1Activation->set_activation_type(ACTIVATE_ON_BOOT);
+    auto metric1ActivationTrigger1 = metric1Activation->add_event_activation();
+    metric1ActivationTrigger1->set_atom_matcher_id(wakelockAcquireMatcher.id());
+    metric1ActivationTrigger1->set_ttl_seconds(100);
+    auto metric1ActivationTrigger2 = metric1Activation->add_event_activation();
+    metric1ActivationTrigger2->set_atom_matcher_id(screenOnMatcher.id());
+    metric1ActivationTrigger2->set_ttl_seconds(200);
+    metric1ActivationTrigger2->set_activation_type(ACTIVATE_IMMEDIATELY);
+
+    ConfigKey cfgKey1(uid, 12341);
+    long timeBase1 = 1;
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(timeBase1, timeBase1, config1, cfgKey1);
+
+    // Metric 1 is not active.
+    // Metric 2 is active.
+    // {{{---------------------------------------------------------------------------
+    EXPECT_EQ(1, processor->mMetricsManagers.size());
+    auto it = processor->mMetricsManagers.find(cfgKey1);
+    EXPECT_TRUE(it != processor->mMetricsManagers.end());
+    auto& metricsManager1 = it->second;
+    EXPECT_TRUE(metricsManager1->isActive());
+
+    auto metricIt = metricsManager1->mAllMetricProducers.begin();
+    for (; metricIt != metricsManager1->mAllMetricProducers.end(); metricIt++) {
+        if ((*metricIt)->getMetricId() == metricId1) {
+            break;
+        }
+    }
+    EXPECT_TRUE(metricIt != metricsManager1->mAllMetricProducers.end());
+    auto& metricProducer1 = *metricIt;
+    EXPECT_FALSE(metricProducer1->isActive());
+
+    metricIt = metricsManager1->mAllMetricProducers.begin();
+    for (; metricIt != metricsManager1->mAllMetricProducers.end(); metricIt++) {
+        if ((*metricIt)->getMetricId() == metricId2) {
+            break;
+        }
+    }
+    EXPECT_TRUE(metricIt != metricsManager1->mAllMetricProducers.end());
+    auto& metricProducer2 = *metricIt;
+    EXPECT_TRUE(metricProducer2->isActive());
+
+    int i = 0;
+    for (; i < metricsManager1->mAllAtomMatchers.size(); i++) {
+        if (metricsManager1->mAllAtomMatchers[i]->getId() ==
+                metric1ActivationTrigger1->atom_matcher_id()) {
+            break;
+        }
+    }
+    const auto& activation1 = metricProducer1->mEventActivationMap.at(i);
+    EXPECT_EQ(100 * NS_PER_SEC, activation1->ttl_ns);
+    EXPECT_EQ(0, activation1->start_ns);
+    EXPECT_EQ(kNotActive, activation1->state);
+    EXPECT_EQ(ACTIVATE_ON_BOOT, activation1->activationType);
+
+    i = 0;
+    for (; i < metricsManager1->mAllAtomMatchers.size(); i++) {
+        if (metricsManager1->mAllAtomMatchers[i]->getId() ==
+                metric1ActivationTrigger2->atom_matcher_id()) {
+            break;
+        }
+    }
+    const auto& activation2 = metricProducer1->mEventActivationMap.at(i);
+    EXPECT_EQ(200 * NS_PER_SEC, activation2->ttl_ns);
+    EXPECT_EQ(0, activation2->start_ns);
+    EXPECT_EQ(kNotActive, activation2->state);
+    EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation2->activationType);
+    // }}}------------------------------------------------------------------------------
+
+    // Trigger Activation 1 for Metric 1
+    std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")};
+    auto event = CreateAcquireWakelockEvent(attributions1, "wl1", 100 + timeBase1);
+    processor->OnLogEvent(event.get());
+
+    // Metric 1 is not active; Activation 1 set to kActiveOnBoot
+    // Metric 2 is active.
+    // {{{---------------------------------------------------------------------------
+    EXPECT_FALSE(metricProducer1->isActive());
+    EXPECT_EQ(0, activation1->start_ns);
+    EXPECT_EQ(kActiveOnBoot, activation1->state);
+    EXPECT_EQ(0, activation2->start_ns);
+    EXPECT_EQ(kNotActive, activation2->state);
+
+    EXPECT_TRUE(metricProducer2->isActive());
+    // }}}-----------------------------------------------------------------------------
+
+    // Simulate shutdown by saving state to disk
+    int64_t shutDownTime = timeBase1 + 100 * NS_PER_SEC;
+    processor->SaveActiveConfigsToDisk(shutDownTime);
+    EXPECT_FALSE(metricProducer1->isActive());
+    int64_t ttl1 = metric1ActivationTrigger1->ttl_seconds() * NS_PER_SEC;
+
+    // Simulate device restarted state by creating new instance of StatsLogProcessor with the
+    // same config.
+    long timeBase2 = 1000;
+    sp<StatsLogProcessor> processor2 =
+            CreateStatsLogProcessor(timeBase2, timeBase2, config1, cfgKey1);
+
+    // Metric 1 is not active.
+    // Metric 2 is active.
+    // {{{---------------------------------------------------------------------------
+    EXPECT_EQ(1, processor2->mMetricsManagers.size());
+    it = processor2->mMetricsManagers.find(cfgKey1);
+    EXPECT_TRUE(it != processor2->mMetricsManagers.end());
+    auto& metricsManager1001 = it->second;
+    EXPECT_TRUE(metricsManager1001->isActive());
+
+    metricIt = metricsManager1001->mAllMetricProducers.begin();
+    for (; metricIt != metricsManager1001->mAllMetricProducers.end(); metricIt++) {
+        if ((*metricIt)->getMetricId() == metricId1) {
+            break;
+        }
+    }
+    EXPECT_TRUE(metricIt != metricsManager1001->mAllMetricProducers.end());
+    auto& metricProducer1001 = *metricIt;
+    EXPECT_FALSE(metricProducer1001->isActive());
+
+    metricIt = metricsManager1001->mAllMetricProducers.begin();
+    for (; metricIt != metricsManager1001->mAllMetricProducers.end(); metricIt++) {
+        if ((*metricIt)->getMetricId() == metricId2) {
+            break;
+        }
+    }
+    EXPECT_TRUE(metricIt != metricsManager1001->mAllMetricProducers.end());
+    auto& metricProducer1002 = *metricIt;
+    EXPECT_TRUE(metricProducer1002->isActive());
+
+    i = 0;
+    for (; i < metricsManager1001->mAllAtomMatchers.size(); i++) {
+        if (metricsManager1001->mAllAtomMatchers[i]->getId() ==
+                metric1ActivationTrigger1->atom_matcher_id()) {
+            break;
+        }
+    }
+    const auto& activation1001_1 = metricProducer1001->mEventActivationMap.at(i);
+    EXPECT_EQ(100 * NS_PER_SEC, activation1001_1->ttl_ns);
+    EXPECT_EQ(0, activation1001_1->start_ns);
+    EXPECT_EQ(kNotActive, activation1001_1->state);
+    EXPECT_EQ(ACTIVATE_ON_BOOT, activation1001_1->activationType);
+
+    i = 0;
+    for (; i < metricsManager1001->mAllAtomMatchers.size(); i++) {
+        if (metricsManager1001->mAllAtomMatchers[i]->getId() ==
+                metric1ActivationTrigger2->atom_matcher_id()) {
+            break;
+        }
+    }
+
+    const auto& activation1001_2 = metricProducer1001->mEventActivationMap.at(i);
+    EXPECT_EQ(200 * NS_PER_SEC, activation1001_2->ttl_ns);
+    EXPECT_EQ(0, activation1001_2->start_ns);
+    EXPECT_EQ(kNotActive, activation1001_2->state);
+    EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation1001_2->activationType);
+    // }}}-----------------------------------------------------------------------------------
+
+    // Load saved state from disk.
+    processor2->LoadActiveConfigsFromDisk();
+
+    // Metric 1 active; Activation 1 is active, Activation 2 is not active
+    // Metric 2 is active.
+    // {{{---------------------------------------------------------------------------
+    EXPECT_TRUE(metricProducer1001->isActive());
+    EXPECT_EQ(timeBase2 + ttl1 - activation1001_1->ttl_ns, activation1001_1->start_ns);
+    EXPECT_EQ(kActive, activation1001_1->state);
+    EXPECT_EQ(0, activation1001_2->start_ns);
+    EXPECT_EQ(kNotActive, activation1001_2->state);
+
+    EXPECT_TRUE(metricProducer1002->isActive());
+    // }}}--------------------------------------------------------------------------------
+
+    // Trigger Activation 2 for Metric 1.
+    auto screenOnEvent = CreateScreenStateChangedEvent(
+            android::view::DISPLAY_STATE_ON,
+            timeBase2 + 200
+    );
+    processor2->OnLogEvent(screenOnEvent.get());
+
+    // Metric 1 active; Activation 1 is active, Activation 2 is active
+    // Metric 2 is active.
+    // {{{---------------------------------------------------------------------------
+    EXPECT_TRUE(metricProducer1001->isActive());
+    EXPECT_EQ(timeBase2 + ttl1 - activation1001_1->ttl_ns, activation1001_1->start_ns);
+    EXPECT_EQ(kActive, activation1001_1->state);
+    EXPECT_EQ(screenOnEvent->GetElapsedTimestampNs(), activation1001_2->start_ns);
+    EXPECT_EQ(kActive, activation1001_2->state);
+
+    EXPECT_TRUE(metricProducer1002->isActive());
+    // }}}---------------------------------------------------------------------------
+
+    // Simulate shutdown by saving state to disk
+    shutDownTime = timeBase2 + 50 * NS_PER_SEC;
+    processor2->SaveActiveConfigsToDisk(shutDownTime);
+    EXPECT_TRUE(metricProducer1001->isActive());
+    EXPECT_TRUE(metricProducer1002->isActive());
+    ttl1 = timeBase2 + metric1ActivationTrigger1->ttl_seconds() * NS_PER_SEC - shutDownTime;
+    int64_t ttl2 = screenOnEvent->GetElapsedTimestampNs() +
+            metric1ActivationTrigger2->ttl_seconds() * NS_PER_SEC - shutDownTime;
+
+    // Simulate device restarted state by creating new instance of StatsLogProcessor with the
+    // same config.
+    long timeBase3 = timeBase2 + 120 * NS_PER_SEC;
+    sp<StatsLogProcessor> processor3 =
+            CreateStatsLogProcessor(timeBase3, timeBase3, config1, cfgKey1);
+
+    // Metric 1 is not active.
+    // Metric 2 is active.
+    // {{{---------------------------------------------------------------------------
+    EXPECT_EQ(1, processor3->mMetricsManagers.size());
+    it = processor3->mMetricsManagers.find(cfgKey1);
+    EXPECT_TRUE(it != processor3->mMetricsManagers.end());
+    auto& metricsManagerTimeBase3 = it->second;
+    EXPECT_TRUE(metricsManagerTimeBase3->isActive());
+
+    metricIt = metricsManagerTimeBase3->mAllMetricProducers.begin();
+    for (; metricIt != metricsManagerTimeBase3->mAllMetricProducers.end(); metricIt++) {
+        if ((*metricIt)->getMetricId() == metricId1) {
+            break;
+        }
+    }
+    EXPECT_TRUE(metricIt != metricsManagerTimeBase3->mAllMetricProducers.end());
+    auto& metricProducerTimeBase3_1 = *metricIt;
+    EXPECT_FALSE(metricProducerTimeBase3_1->isActive());
+
+    metricIt = metricsManagerTimeBase3->mAllMetricProducers.begin();
+    for (; metricIt != metricsManagerTimeBase3->mAllMetricProducers.end(); metricIt++) {
+        if ((*metricIt)->getMetricId() == metricId2) {
+            break;
+        }
+    }
+    EXPECT_TRUE(metricIt != metricsManagerTimeBase3->mAllMetricProducers.end());
+    auto& metricProducerTimeBase3_2 = *metricIt;
+    EXPECT_TRUE(metricProducerTimeBase3_2->isActive());
+
+    i = 0;
+    for (; i < metricsManagerTimeBase3->mAllAtomMatchers.size(); i++) {
+        if (metricsManagerTimeBase3->mAllAtomMatchers[i]->getId() ==
+                metric1ActivationTrigger1->atom_matcher_id()) {
+            break;
+        }
+    }
+    const auto& activationTimeBase3_1 = metricProducerTimeBase3_1->mEventActivationMap.at(i);
+    EXPECT_EQ(100 * NS_PER_SEC, activationTimeBase3_1->ttl_ns);
+    EXPECT_EQ(0, activationTimeBase3_1->start_ns);
+    EXPECT_EQ(kNotActive, activationTimeBase3_1->state);
+    EXPECT_EQ(ACTIVATE_ON_BOOT, activationTimeBase3_1->activationType);
+
+    i = 0;
+    for (; i < metricsManagerTimeBase3->mAllAtomMatchers.size(); i++) {
+        if (metricsManagerTimeBase3->mAllAtomMatchers[i]->getId() ==
+                metric1ActivationTrigger2->atom_matcher_id()) {
+            break;
+        }
+    }
+
+    const auto& activationTimeBase3_2 = metricProducerTimeBase3_1->mEventActivationMap.at(i);
+    EXPECT_EQ(200 * NS_PER_SEC, activationTimeBase3_2->ttl_ns);
+    EXPECT_EQ(0, activationTimeBase3_2->start_ns);
+    EXPECT_EQ(kNotActive, activationTimeBase3_2->state);
+    EXPECT_EQ(ACTIVATE_IMMEDIATELY, activationTimeBase3_2->activationType);
+    // }}}----------------------------------------------------------------------------------
+
+    // Load saved state from disk.
+    processor3->LoadActiveConfigsFromDisk();
+
+    // Metric 1 active: Activation 1 is active, Activation 2 is active
+    // Metric 2 is active.
+    // {{{---------------------------------------------------------------------------
+    EXPECT_TRUE(metricProducerTimeBase3_1->isActive());
+    EXPECT_EQ(timeBase3 + ttl1 - activationTimeBase3_1->ttl_ns, activationTimeBase3_1->start_ns);
+    EXPECT_EQ(kActive, activationTimeBase3_1->state);
+    EXPECT_EQ(timeBase3 + ttl2 - activationTimeBase3_2->ttl_ns, activationTimeBase3_2->start_ns);
+    EXPECT_EQ(kActive, activationTimeBase3_2->state);
+
+    EXPECT_TRUE(metricProducerTimeBase3_2->isActive());
+    // }}}-------------------------------------------------------------------------------
+
+
+    // Trigger Activation 2 for Metric 1 again.
+    screenOnEvent = CreateScreenStateChangedEvent(
+            android::view::DISPLAY_STATE_ON,
+            timeBase3 + 100 * NS_PER_SEC
+    );
+    processor3->OnLogEvent(screenOnEvent.get());
+
+    // Metric 1 active; Activation 1 is not active, Activation 2 is set to active
+    // Metric 2 is active.
+    // {{{---------------------------------------------------------------------------
+    EXPECT_TRUE(metricProducerTimeBase3_1->isActive());
+    EXPECT_EQ(kNotActive, activationTimeBase3_1->state);
+    EXPECT_EQ(screenOnEvent->GetElapsedTimestampNs(), activationTimeBase3_2->start_ns);
+    EXPECT_EQ(kActive, activationTimeBase3_2->state);
+
+    EXPECT_TRUE(metricProducerTimeBase3_2->isActive());
+    // }}}---------------------------------------------------------------------------
 }
 
 #else
diff --git a/cmds/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp b/cmds/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
index f01ad06..6ec0a11 100644
--- a/cmds/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
+++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
@@ -418,7 +418,7 @@
     const int64_t ttlNs = 2 * bucketSizeNs; // Two buckets.
     auto metric_activation = config.add_metric_activation();
     metric_activation->set_metric_id(metricId);
-    metric_activation->set_activation_type(MetricActivation::ACTIVATE_IMMEDIATELY);
+    metric_activation->set_activation_type(ACTIVATE_IMMEDIATELY);
     auto event_activation = metric_activation->add_event_activation();
     event_activation->set_atom_matcher_id(batterySaverStartMatcher.id());
     event_activation->set_ttl_seconds(ttlNs / 1000000000);
diff --git a/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp b/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp
index bf52bb0..d99d281 100644
--- a/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp
@@ -245,10 +245,10 @@
     EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end());
     EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end());
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, 0);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, 0);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
 
     std::unique_ptr<LogEvent> event;
@@ -268,10 +268,10 @@
     EXPECT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, 0);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
 
     // First processed event.
@@ -285,10 +285,10 @@
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
 
     // 2nd processed event.
@@ -298,10 +298,10 @@
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     // No new broadcast since the config should still be active.
     EXPECT_EQ(broadcastCount, 1);
@@ -319,10 +319,10 @@
     EXPECT_EQ(broadcastCount, 2);
     EXPECT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
 
     // Re-activate metric via screen on.
@@ -335,10 +335,10 @@
     EXPECT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
 
     // 4th processed event.
@@ -460,10 +460,10 @@
     EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end());
     EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end());
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, 0);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, 0);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap.size(), 1u);
     EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end());
@@ -486,10 +486,10 @@
     EXPECT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, 0);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
 
@@ -504,10 +504,10 @@
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
 
@@ -518,10 +518,10 @@
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
     // No new broadcast since the config should still be active.
@@ -540,10 +540,10 @@
     EXPECT_EQ(broadcastCount, 2);
     EXPECT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
 
@@ -557,10 +557,10 @@
     EXPECT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
 
@@ -577,10 +577,10 @@
     EXPECT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
 
@@ -597,10 +597,10 @@
     EXPECT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
 
@@ -613,10 +613,10 @@
     EXPECT_EQ(broadcastCount, 4);
     EXPECT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
 
@@ -632,10 +632,10 @@
     EXPECT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
 
@@ -647,10 +647,10 @@
     EXPECT_EQ(broadcastCount, 6);
     EXPECT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
 
@@ -782,10 +782,10 @@
     EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end());
     EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end());
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, 0);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, 0);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap.size(), 2u);
     EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end());
@@ -810,10 +810,10 @@
     EXPECT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, 0);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
     EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
@@ -829,10 +829,10 @@
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
     EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
@@ -844,10 +844,10 @@
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
     EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
@@ -867,10 +867,10 @@
     EXPECT_EQ(broadcastCount, 2);
     EXPECT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
     EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
@@ -885,10 +885,10 @@
     EXPECT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
     EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
@@ -906,10 +906,10 @@
     EXPECT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
     EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
@@ -927,10 +927,10 @@
     EXPECT_EQ(broadcastCount, 4);
     EXPECT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
     EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
@@ -943,10 +943,10 @@
     EXPECT_EQ(broadcastCount, 4);
     EXPECT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
     EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
@@ -963,10 +963,10 @@
     EXPECT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
     EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
@@ -979,10 +979,10 @@
     EXPECT_EQ(broadcastCount, 6);
     EXPECT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
     EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
@@ -1119,10 +1119,10 @@
     EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end());
     EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end());
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, 0);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, 0);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap.size(), 2u);
     EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end());
@@ -1134,10 +1134,10 @@
     EXPECT_TRUE(eventActivationMap2.find(0) != eventActivationMap2.end());
     EXPECT_TRUE(eventActivationMap2.find(2) != eventActivationMap2.end());
     EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap2[0]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap2[0]->start_ns, 0);
     EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap2[2]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap2[2]->start_ns, 0);
     EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap2.size(), 2u);
     EXPECT_TRUE(eventDeactivationMap2.find(3) != eventDeactivationMap2.end());
@@ -1165,19 +1165,19 @@
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, 0);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
     EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
     EXPECT_TRUE(metricProducer2->mIsActive);
     EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap2[2]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap2[2]->start_ns, 0);
     EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]);
     EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]);
@@ -1195,19 +1195,19 @@
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
     EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
     EXPECT_TRUE(metricProducer2->mIsActive);
     EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + 20);
     EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]);
     EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]);
@@ -1221,19 +1221,19 @@
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
     EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
     EXPECT_TRUE(metricProducer2->mIsActive);
     EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + 20);
     EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]);
     EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]);
@@ -1257,19 +1257,19 @@
     EXPECT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
     EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
     EXPECT_FALSE(metricProducer2->mIsActive);
     EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + 20);
     EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]);
     EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]);
@@ -1284,19 +1284,19 @@
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
     EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
     EXPECT_TRUE(metricProducer2->mIsActive);
     EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + 10);
     EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]);
     EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]);
@@ -1316,19 +1316,19 @@
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
     EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
     EXPECT_TRUE(metricProducer2->mIsActive);
     EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
     EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]);
     EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]);
@@ -1348,19 +1348,19 @@
     EXPECT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
     EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
     EXPECT_FALSE(metricProducer2->mIsActive);
     EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
     EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]);
     EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]);
@@ -1375,19 +1375,19 @@
     EXPECT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
     EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
     EXPECT_FALSE(metricProducer2->mIsActive);
     EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
     EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]);
     EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]);
@@ -1406,19 +1406,19 @@
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
     EXPECT_TRUE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
     EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
     EXPECT_TRUE(metricProducer2->mIsActive);
     EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
+    EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
     EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]);
     EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]);
@@ -1431,19 +1431,19 @@
     EXPECT_EQ(activeConfigsBroadcast.size(), 0);
     EXPECT_FALSE(metricProducer->mIsActive);
     EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
+    EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
     EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
     EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
     EXPECT_FALSE(metricProducer2->mIsActive);
     EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
+    EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
     EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
     EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]);
     EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]);
diff --git a/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp b/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
index e967eb3..ff6af38 100644
--- a/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
@@ -279,7 +279,7 @@
     const int64_t ttlNs = 2 * bucketSizeNs; // Two buckets.
     auto metric_activation = config.add_metric_activation();
     metric_activation->set_metric_id(metricId);
-    metric_activation->set_activation_type(MetricActivation::ACTIVATE_IMMEDIATELY);
+    metric_activation->set_activation_type(ACTIVATE_IMMEDIATELY);
     auto event_activation = metric_activation->add_event_activation();
     event_activation->set_atom_matcher_id(batterySaverStartMatcher.id());
     event_activation->set_ttl_seconds(ttlNs / 1000000000);
diff --git a/core/java/android/content/om/IOverlayManager.aidl b/core/java/android/content/om/IOverlayManager.aidl
index 722c128..43a4fe5 100644
--- a/core/java/android/content/om/IOverlayManager.aidl
+++ b/core/java/android/content/om/IOverlayManager.aidl
@@ -152,4 +152,9 @@
      * @param userId The user for which to change the overlay.
      */
     boolean setLowestPriority(in String packageName, in int userId);
+
+    /**
+     * Returns the list of default overlay packages, or an empty array if there are none.
+     */
+    String[] getDefaultOverlayPackages();
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ced6115..8a21806 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1791,6 +1791,58 @@
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_MANAGE_DOMAIN_URLS = "android.settings.MANAGE_DOMAIN_URLS";
 
+    /**
+     * Broadcast to trigger notification of asking user to enable MMS.
+     * Need to specify {@link #EXTRA_ENABLE_MMS_DATA_REQUEST_REASON} and {@link #EXTRA_SUB_ID}.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_ENABLE_MMS_DATA_REQUEST =
+            "android.settings.ENABLE_MMS_DATA_REQUEST";
+
+    /**
+     * Integer value that specifies the reason triggering enable MMS data notification.
+     * This must be passed as an extra field to the {@link #ACTION_ENABLE_MMS_DATA_REQUEST}.
+     * Extra with value of EnableMmsDataReason interface.
+     * @hide
+     */
+    public static final String EXTRA_ENABLE_MMS_DATA_REQUEST_REASON =
+            "android.settings.extra.ENABLE_MMS_DATA_REQUEST_REASON";
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "ENABLE_MMS_DATA_REQUEST_REASON_" }, value = {
+            ENABLE_MMS_DATA_REQUEST_REASON_INCOMING_MMS,
+            ENABLE_MMS_DATA_REQUEST_REASON_OUTGOING_MMS,
+    })
+    public @interface EnableMmsDataReason{}
+
+    /**
+     * Requesting to enable MMS data because there's an incoming MMS.
+     * @hide
+     */
+    public static final int ENABLE_MMS_DATA_REQUEST_REASON_INCOMING_MMS = 0;
+
+    /**
+     * Requesting to enable MMS data because user is sending MMS.
+     * @hide
+     */
+    public static final int ENABLE_MMS_DATA_REQUEST_REASON_OUTGOING_MMS = 1;
+
+    /**
+     * Activity Action: Show screen of a cellular subscription and highlight the
+     * "enable MMS" toggle.
+     * <p>
+     * Input: {@link #EXTRA_SUB_ID}: Sub ID of the subscription.
+     * <p>
+     * Output: Nothing
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_MMS_MESSAGE_SETTING = "android.settings.MMS_MESSAGE_SETTING";
+
     // End of Intent actions for Settings
 
     /**
diff --git a/core/java/com/android/internal/app/AbstractResolverComparator.java b/core/java/com/android/internal/app/AbstractResolverComparator.java
index b7276a0..87d80d4 100644
--- a/core/java/com/android/internal/app/AbstractResolverComparator.java
+++ b/core/java/com/android/internal/app/AbstractResolverComparator.java
@@ -22,9 +22,14 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.os.UserHandle;
 import android.util.Log;
+
 import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
+
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
@@ -35,6 +40,8 @@
 abstract class AbstractResolverComparator implements Comparator<ResolvedComponentInfo> {
 
     private static final int NUM_OF_TOP_ANNOTATIONS_TO_USE = 3;
+    private static final boolean DEBUG = false;
+    private static final String TAG = "AbstractResolverComp";
 
     private AfterCompute mAfterCompute;
     protected final PackageManager mPm;
@@ -47,6 +54,46 @@
     // can be null if mHttp == false or current user has no default browser package
     private final String mDefaultBrowserPackageName;
 
+    // message types
+    static final int RANKER_SERVICE_RESULT = 0;
+    static final int RANKER_RESULT_TIMEOUT = 1;
+
+    // timeout for establishing connections with a ResolverRankerService, collecting features and
+    // predicting ranking scores.
+    private static final int WATCHDOG_TIMEOUT_MILLIS = 500;
+
+    protected final Handler mHandler = new Handler(Looper.getMainLooper()) {
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case RANKER_SERVICE_RESULT:
+                    if (DEBUG) {
+                        Log.d(TAG, "RANKER_SERVICE_RESULT");
+                    }
+                    if (mHandler.hasMessages(RANKER_RESULT_TIMEOUT)) {
+                        if (msg.obj != null) {
+                            handleResultMessage(msg);
+                        } else {
+                            Log.e(TAG, "Receiving null prediction results.");
+                        }
+                        mHandler.removeMessages(RANKER_RESULT_TIMEOUT);
+                        afterCompute();
+                    }
+                    break;
+
+                case RANKER_RESULT_TIMEOUT:
+                    if (DEBUG) {
+                        Log.d(TAG, "RANKER_RESULT_TIMEOUT; unbinding services");
+                    }
+                    mHandler.removeMessages(RANKER_SERVICE_RESULT);
+                    afterCompute();
+                    break;
+
+                default:
+                    super.handleMessage(msg);
+            }
+        }
+    };
+
     AbstractResolverComparator(Context context, Intent intent) {
         String scheme = intent.getScheme();
         mHttp = "http".equals(scheme) || "https".equals(scheme);
@@ -142,9 +189,16 @@
      * #getScore(ComponentName)} or {@link #compare(Object, Object)}, in order to prepare the
      * comparator for those calls. Note that {@link #getScore(ComponentName)} uses {@link
      * ComponentName}, so the implementation will have to be prepared to identify a {@link
-     * ResolvedComponentInfo} by {@link ComponentName}.
+     * ResolvedComponentInfo} by {@link ComponentName}. {@link #beforeCompute()} will be called
+     * before doing any computing.
      */
-    abstract void compute(List<ResolvedComponentInfo> targets);
+    final void compute(List<ResolvedComponentInfo> targets) {
+        beforeCompute();
+        doCompute(targets);
+    }
+
+    /** Implementation of compute called after {@link #beforeCompute()}. */
+    abstract void doCompute(List<ResolvedComponentInfo> targets);
 
     /**
      * Returns the score that was calculated for the corresponding {@link ResolvedComponentInfo}
@@ -152,6 +206,9 @@
      */
     abstract float getScore(ComponentName name);
 
+    /** Handles result message sent to mHandler. */
+    abstract void handleResultMessage(Message message);
+
     /**
      * Reports to UsageStats what was chosen.
      */
@@ -172,10 +229,26 @@
     void updateModel(ComponentName componentName) {
     }
 
+    /** Called before {@link #doCompute(List)}. Sets up 500ms timeout. */
+    void beforeCompute() {
+        if (DEBUG) Log.d(TAG, "Setting watchdog timer for " + WATCHDOG_TIMEOUT_MILLIS + "ms");
+        if (mHandler == null) {
+            Log.d(TAG, "Error: Handler is Null; Needs to be initialized.");
+            return;
+        }
+        mHandler.sendEmptyMessageDelayed(RANKER_RESULT_TIMEOUT, WATCHDOG_TIMEOUT_MILLIS);
+    }
+
     /**
-     * Called when the {@link ResolverActivity} is destroyed.
+     * Called when the {@link ResolverActivity} is destroyed. This calls {@link #afterCompute()}. If
+     * this call needs to happen at a different time during destroy, the method should be
+     * overridden.
      */
-    abstract void destroy();
+    void destroy() {
+        mHandler.removeMessages(RANKER_SERVICE_RESULT);
+        mHandler.removeMessages(RANKER_RESULT_TIMEOUT);
+        afterCompute();
+    }
 
     private boolean isDefaultBrowser(ResolveInfo ri) {
         // It makes sense to prefer the default browser
diff --git a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
index cb44c67..3b4e1a0 100644
--- a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
+++ b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
+import android.os.Message;
 import android.os.UserHandle;
 import android.view.textclassifier.Log;
 
@@ -73,7 +74,7 @@
     }
 
     @Override
-    void compute(List<ResolvedComponentInfo> targets) {
+    void doCompute(List<ResolvedComponentInfo> targets) {
         List<AppTarget> appTargets = new ArrayList<>();
         for (ResolvedComponentInfo target : targets) {
             appTargets.add(new AppTarget.Builder(new AppTargetId(target.name.flattenToString()))
@@ -82,15 +83,24 @@
         }
         mAppPredictor.sortTargets(appTargets, mContext.getMainExecutor(),
                 sortedAppTargets -> {
-                    for (int i = 0; i < sortedAppTargets.size(); i++) {
-                        mTargetRanks.put(new ComponentName(sortedAppTargets.get(i).getPackageName(),
-                                sortedAppTargets.get(i).getClassName()), i);
-                    }
-                    afterCompute();
+                    Message msg =
+                            Message.obtain(mHandler, RANKER_SERVICE_RESULT, sortedAppTargets);
+                    msg.sendToTarget();
                 });
     }
 
     @Override
+    void handleResultMessage(Message msg) {
+        if (msg.what == RANKER_SERVICE_RESULT) {
+            final List<AppTarget> sortedAppTargets = (List<AppTarget>) msg.obj;
+            for (int i = 0; i < sortedAppTargets.size(); i++) {
+                mTargetRanks.put(new ComponentName(sortedAppTargets.get(i).getPackageName(),
+                        sortedAppTargets.get(i).getClassName()), i);
+            }
+        }
+    }
+
+    @Override
     float getScore(ComponentName name) {
         Integer rank = mTargetRanks.get(name);
         if (rank == null) {
@@ -111,9 +121,4 @@
                         .setClassName(componentName.getClassName()).build(),
                     ACTION_LAUNCH).build());
     }
-
-    @Override
-    void destroy() {
-        // Do nothing. App Predictor destruction is handled by caller.
-    }
 }
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 204012f..8b510a4 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -114,6 +114,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.ImageUtils;
+import com.android.internal.widget.ResolverDrawerLayout;
 
 import com.google.android.collect.Lists;
 
@@ -143,6 +144,8 @@
     public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP
             = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP";
 
+    private static final String PREF_NUM_SHEET_EXPANSIONS = "pref_num_sheet_expansions";
+
     private static final boolean DEBUG = false;
 
     /**
@@ -502,6 +505,21 @@
                     chooserHeader.setElevation(defaultElevation);
                 }
             });
+
+            mResolverDrawerLayout.setOnCollapsedChangedListener(
+                    new ResolverDrawerLayout.OnCollapsedChangedListener() {
+
+                        // Only consider one expansion per activity creation
+                        private boolean mWrittenOnce = false;
+
+                        @Override
+                        public void onCollapsedChanged(boolean isCollapsed) {
+                            if (!isCollapsed && !mWrittenOnce) {
+                                incrementNumSheetExpansions();
+                                mWrittenOnce = true;
+                            }
+                        }
+                    });
         }
 
         if (DEBUG) {
@@ -881,6 +899,15 @@
         return CONTENT_PREVIEW_TEXT;
     }
 
+    private int getNumSheetExpansions() {
+        return getPreferences(Context.MODE_PRIVATE).getInt(PREF_NUM_SHEET_EXPANSIONS, 0);
+    }
+
+    private void incrementNumSheetExpansions() {
+        getPreferences(Context.MODE_PRIVATE).edit().putInt(PREF_NUM_SHEET_EXPANSIONS,
+                getNumSheetExpansions() + 1).apply();
+    }
+
     @Override
     protected void onDestroy() {
         super.onDestroy();
@@ -2030,7 +2057,8 @@
         public static final int TARGET_STANDARD_AZ = 3;
 
         private static final int MAX_SUGGESTED_APP_TARGETS = 4;
-        private static final int MAX_TARGETS_PER_SERVICE = 2;
+        private static final int MAX_CHOOSER_TARGETS_PER_APP = 2;
+        private static final int MAX_SHORTCUT_TARGETS_PER_APP = 8;
 
         private static final int MAX_SERVICE_TARGETS = 8;
 
@@ -2356,9 +2384,11 @@
             final float baseScore = getBaseScore(origTarget, isShortcutResult);
             Collections.sort(targets, mBaseTargetComparator);
 
+            final int maxTargets = isShortcutResult ? MAX_SHORTCUT_TARGETS_PER_APP
+                                       : MAX_CHOOSER_TARGETS_PER_APP;
             float lastScore = 0;
             boolean shouldNotify = false;
-            for (int i = 0, N = Math.min(targets.size(), MAX_TARGETS_PER_SERVICE); i < N; i++) {
+            for (int i = 0, count = Math.min(targets.size(), maxTargets); i < count; i++) {
                 final ChooserTarget target = targets.get(i);
                 float targetScore = target.getScore();
                 targetScore *= baseScore;
@@ -2491,19 +2521,25 @@
 
         private DirectShareViewHolder mDirectShareViewHolder;
         private int mChooserTargetWidth = 0;
+        private boolean mShowAzLabelIfPoss;
 
         private static final int VIEW_TYPE_DIRECT_SHARE = 0;
         private static final int VIEW_TYPE_NORMAL = 1;
         private static final int VIEW_TYPE_CONTENT_PREVIEW = 2;
         private static final int VIEW_TYPE_PROFILE = 3;
+        private static final int VIEW_TYPE_AZ_LABEL = 4;
 
         private static final int MAX_TARGETS_PER_ROW_PORTRAIT = 4;
         private static final int MAX_TARGETS_PER_ROW_LANDSCAPE = 8;
 
+        private static final int NUM_EXPANSIONS_TO_HIDE_AZ_LABEL = 20;
+
         public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) {
             mChooserListAdapter = wrappedAdapter;
             mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
 
+            mShowAzLabelIfPoss = getNumSheetExpansions() < NUM_EXPANSIONS_TO_HIDE_AZ_LABEL;
+
             wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
                 @Override
                 public void onChanged() {
@@ -2556,6 +2592,7 @@
                             + getProfileRowCount()
                             + getServiceTargetRowCount()
                             + getCallerAndRankedTargetRowCount()
+                            + getAzLabelRowCount()
                             + Math.ceil(
                             (float) mChooserListAdapter.getAlphaTargetCount()
                                     / getMaxTargetsPerRow())
@@ -2593,6 +2630,11 @@
             return 0;
         }
 
+        public int getAzLabelRowCount() {
+            // Only show a label if the a-z list is showing
+            return (mShowAzLabelIfPoss && mChooserListAdapter.getAlphaTargetCount() > 0) ? 1 : 0;
+        }
+
         @Override
         public Object getItem(int position) {
             // We have nothing useful to return here.
@@ -2617,6 +2659,10 @@
                 return createProfileView(convertView, parent);
             }
 
+            if (viewType == VIEW_TYPE_AZ_LABEL) {
+                return createAzLabelView(parent);
+            }
+
             if (convertView == null) {
                 holder = createViewHolder(viewType, parent);
             } else {
@@ -2630,27 +2676,29 @@
 
         @Override
         public int getItemViewType(int position) {
-            if (position == 0 && getContentPreviewRowCount() == 1) {
-                return VIEW_TYPE_CONTENT_PREVIEW;
-            }
+            int count;
 
-            if (getProfileRowCount() == 1 && position == getContentPreviewRowCount()) {
-                return VIEW_TYPE_PROFILE;
-            }
+            int countSum = (count = getContentPreviewRowCount());
+            if (count > 0 && position < countSum) return VIEW_TYPE_CONTENT_PREVIEW;
 
-            final int start = getFirstRowPosition(position);
-            final int startType = mChooserListAdapter.getPositionTargetType(start);
+            countSum += (count = getProfileRowCount());
+            if (count > 0 && position < countSum) return VIEW_TYPE_PROFILE;
 
-            if (startType == ChooserListAdapter.TARGET_SERVICE) {
-                return VIEW_TYPE_DIRECT_SHARE;
-            }
+            countSum += (count = getServiceTargetRowCount());
+            if (count > 0 && position < countSum) return VIEW_TYPE_DIRECT_SHARE;
+
+            countSum += (count = getCallerAndRankedTargetRowCount());
+            if (count > 0 && position < countSum) return VIEW_TYPE_NORMAL;
+
+            countSum += (count = getAzLabelRowCount());
+            if (count > 0 && position < countSum) return VIEW_TYPE_AZ_LABEL;
 
             return VIEW_TYPE_NORMAL;
         }
 
         @Override
         public int getViewTypeCount() {
-            return 4;
+            return 5;
         }
 
         private ViewGroup createContentPreviewView(View convertView, ViewGroup parent) {
@@ -2677,6 +2725,10 @@
             return profileRow;
         }
 
+        private View createAzLabelView(ViewGroup parent) {
+            return mLayoutInflater.inflate(R.layout.chooser_az_label_row, parent, false);
+        }
+
         private RowViewHolder loadViewsIntoRow(RowViewHolder holder) {
             final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
             final int exactSpec = MeasureSpec.makeMeasureSpec(mChooserTargetWidth,
@@ -2775,16 +2827,24 @@
         }
 
         /**
-         * Need to merge CALLER + ranked STANDARD into a single row. All other types
-         * are placed into their own row as determined by their target type, and dividers
-         * are added in the list to separate each type.
+         * Need to merge CALLER + ranked STANDARD into a single row and prevent a separator from
+         * showing on top of the AZ list if the AZ label is visible. All other types are placed into
+         * their own row as determined by their target type, and dividers are added in the list to
+         * separate each type.
          */
         int getRowType(int rowPosition) {
+            // Merge caller and ranked standard into a single row
             int positionType = mChooserListAdapter.getPositionTargetType(rowPosition);
             if (positionType == ChooserListAdapter.TARGET_CALLER) {
                 return ChooserListAdapter.TARGET_STANDARD;
             }
 
+            // If an the A-Z label is shown, prevent a separator from appearing by making the A-Z
+            // row type the same as the suggestion row type
+            if (getAzLabelRowCount() > 0 && positionType == ChooserListAdapter.TARGET_STANDARD_AZ) {
+                return ChooserListAdapter.TARGET_STANDARD;
+            }
+
             return positionType;
         }
 
@@ -2864,6 +2924,8 @@
                 return serviceCount + (row - serviceRows) * getMaxTargetsPerRow();
             }
 
+            row -= getAzLabelRowCount();
+
             return callerAndRankedCount + serviceCount
                     + (row - callerAndRankedRows - serviceRows) * getMaxTargetsPerRow();
         }
diff --git a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java
index 726b186..a781907 100644
--- a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java
+++ b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java
@@ -18,7 +18,6 @@
 package com.android.internal.app;
 
 import android.app.usage.UsageStats;
-import android.app.usage.UsageStatsManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -28,9 +27,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.metrics.LogMaker;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -67,10 +64,6 @@
 
     private static final float RECENCY_MULTIPLIER = 2.f;
 
-    // message types
-    private static final int RESOLVER_RANKER_SERVICE_RESULT = 0;
-    private static final int RESOLVER_RANKER_RESULT_TIMEOUT = 1;
-
     // timeout for establishing connections with a ResolverRankerService.
     private static final int CONNECTION_COST_TIMEOUT_MILLIS = 200;
     // timeout for establishing connections with a ResolverRankerService, collecting features and
@@ -93,57 +86,6 @@
     private Context mContext;
     private CountDownLatch mConnectSignal;
 
-    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case RESOLVER_RANKER_SERVICE_RESULT:
-                    if (DEBUG) {
-                        Log.d(TAG, "RESOLVER_RANKER_SERVICE_RESULT");
-                    }
-                    if (mHandler.hasMessages(RESOLVER_RANKER_RESULT_TIMEOUT)) {
-                        if (msg.obj != null) {
-                            final List<ResolverTarget> receivedTargets =
-                                    (List<ResolverTarget>) msg.obj;
-                            if (receivedTargets != null && mTargets != null
-                                    && receivedTargets.size() == mTargets.size()) {
-                                final int size = mTargets.size();
-                                boolean isUpdated = false;
-                                for (int i = 0; i < size; ++i) {
-                                    final float predictedProb =
-                                            receivedTargets.get(i).getSelectProbability();
-                                    if (predictedProb != mTargets.get(i).getSelectProbability()) {
-                                        mTargets.get(i).setSelectProbability(predictedProb);
-                                        isUpdated = true;
-                                    }
-                                }
-                                if (isUpdated) {
-                                    mRankerServiceName = mResolvedRankerName;
-                                }
-                            } else {
-                                Log.e(TAG, "Sizes of sent and received ResolverTargets diff.");
-                            }
-                        } else {
-                            Log.e(TAG, "Receiving null prediction results.");
-                        }
-                        mHandler.removeMessages(RESOLVER_RANKER_RESULT_TIMEOUT);
-                        afterCompute();
-                    }
-                    break;
-
-                case RESOLVER_RANKER_RESULT_TIMEOUT:
-                    if (DEBUG) {
-                        Log.d(TAG, "RESOLVER_RANKER_RESULT_TIMEOUT; unbinding services");
-                    }
-                    mHandler.removeMessages(RESOLVER_RANKER_SERVICE_RESULT);
-                    afterCompute();
-                    break;
-
-                default:
-                    super.handleMessage(msg);
-            }
-        }
-    };
-
     public ResolverRankerServiceResolverComparator(Context context, Intent intent,
                 String referrerPackage, AfterCompute afterCompute) {
         super(context, intent);
@@ -159,11 +101,35 @@
         setCallBack(afterCompute);
     }
 
+    @Override
+    public void handleResultMessage(Message msg) {
+        if (msg.what != RANKER_SERVICE_RESULT) {
+            return;
+        }
+        final List<ResolverTarget> receivedTargets = (List<ResolverTarget>) msg.obj;
+        if (receivedTargets != null && mTargets != null
+                    && receivedTargets.size() == mTargets.size()) {
+            final int size = mTargets.size();
+            boolean isUpdated = false;
+            for (int i = 0; i < size; ++i) {
+                final float predictedProb =
+                        receivedTargets.get(i).getSelectProbability();
+                if (predictedProb != mTargets.get(i).getSelectProbability()) {
+                    mTargets.get(i).setSelectProbability(predictedProb);
+                    isUpdated = true;
+                }
+            }
+            if (isUpdated) {
+                mRankerServiceName = mResolvedRankerName;
+            }
+        } else {
+            Log.e(TAG, "Sizes of sent and received ResolverTargets diff.");
+        }
+    }
+
     // compute features for each target according to usage stats of targets.
     @Override
-    public void compute(List<ResolvedComponentInfo> targets) {
-        reset();
-
+    public void doCompute(List<ResolvedComponentInfo> targets) {
         final long recentSinceTime = mCurrentTime - RECENCY_TIME_PERIOD;
 
         float mostRecencyScore = 1.0f;
@@ -322,8 +288,8 @@
     // unbind the service and clear unhandled messges.
     @Override
     public void destroy() {
-        mHandler.removeMessages(RESOLVER_RANKER_SERVICE_RESULT);
-        mHandler.removeMessages(RESOLVER_RANKER_RESULT_TIMEOUT);
+        mHandler.removeMessages(RANKER_SERVICE_RESULT);
+        mHandler.removeMessages(RANKER_RESULT_TIMEOUT);
         if (mConnection != null) {
             mContext.unbindService(mConnection);
             mConnection.destroy();
@@ -417,15 +383,6 @@
         return null;
     }
 
-    // set a watchdog, to avoid waiting for ranking service for too long.
-    private void startWatchDog(int timeOutLimit) {
-        if (DEBUG) Log.d(TAG, "Setting watchdog timer for " + timeOutLimit + "ms");
-        if (mHandler == null) {
-            Log.d(TAG, "Error: Handler is Null; Needs to be initialized.");
-        }
-        mHandler.sendEmptyMessageDelayed(RESOLVER_RANKER_RESULT_TIMEOUT, timeOutLimit);
-    }
-
     private class ResolverRankerServiceConnection implements ServiceConnection {
         private final CountDownLatch mConnectSignal;
 
@@ -442,7 +399,7 @@
                 }
                 synchronized (mLock) {
                     final Message msg = Message.obtain();
-                    msg.what = RESOLVER_RANKER_SERVICE_RESULT;
+                    msg.what = RANKER_SERVICE_RESULT;
                     msg.obj = targets;
                     mHandler.sendMessage(msg);
                 }
@@ -477,12 +434,13 @@
         }
     }
 
-    private void reset() {
+    @Override
+    void beforeCompute() {
+        super.beforeCompute();
         mTargetsDict.clear();
         mTargets = null;
         mRankerServiceName = new ComponentName(mContext, this.getClass());
         mResolvedRankerName = null;
-        startWatchDog(WATCHDOG_TIMEOUT_MILLIS);
         initRanker(mContext);
     }
 
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index 6b0d85e..3adb36f 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -104,6 +104,7 @@
 
     private OnDismissedListener mOnDismissedListener;
     private RunOnDismissedListener mRunOnDismissedListener;
+    private OnCollapsedChangedListener mOnCollapsedChangedListener;
 
     private boolean mDismissLocked;
 
@@ -267,6 +268,10 @@
         return mOnDismissedListener != null && !mDismissLocked;
     }
 
+    public void setOnCollapsedChangedListener(OnCollapsedChangedListener listener) {
+        mOnCollapsedChangedListener = listener;
+    }
+
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         final int action = ev.getActionMasked();
@@ -548,6 +553,10 @@
         if (mScrollIndicatorDrawable != null) {
             setWillNotDraw(!isCollapsed);
         }
+
+        if (mOnCollapsedChangedListener != null) {
+            mOnCollapsedChangedListener.onCollapsedChanged(isCollapsed);
+        }
     }
 
     void dispatchOnDismissed() {
@@ -1078,8 +1087,25 @@
         };
     }
 
+    /**
+     * Listener for sheet dismissed events.
+     */
     public interface OnDismissedListener {
-        public void onDismissed();
+        /**
+         * Callback when the sheet is dismissed by the user.
+         */
+        void onDismissed();
+    }
+
+    /**
+     * Listener for sheet collapsed / expanded events.
+     */
+    public interface OnCollapsedChangedListener {
+        /**
+         * Callback when the sheet is either fully expanded or collapsed.
+         * @param isCollapsed true when collapsed, false when expanded.
+         */
+        void onCollapsedChanged(boolean isCollapsed);
     }
 
     private class RunOnDismissedListener implements Runnable {
diff --git a/core/res/res/layout/chooser_az_label_row.xml b/core/res/res/layout/chooser_az_label_row.xml
new file mode 100644
index 0000000..1b733fc
--- /dev/null
+++ b/core/res/res/layout/chooser_az_label_row.xml
@@ -0,0 +1,31 @@
+<?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
+  -->
+
+<!-- Separator applied as background -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+          android:text="@string/chooser_all_apps_button_label"
+          android:contentDescription="@string/chooser_all_apps_button_label"
+          android:background="@drawable/chooser_row_layer_list"
+          android:textAppearance="?attr/textAppearanceSmall"
+          android:textColor="?attr/textColorSecondary"
+          android:textSize="14sp"
+          android:singleLine="true"
+          android:paddingTop="16dp"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:gravity="center"/>
+
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ad3203e..04ccb74 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3863,10 +3863,6 @@
          {@see android.view.Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS} -->
     <string name="config_secondaryHomeComponent" translatable="false">com.android.launcher3/com.android.launcher3.SecondaryDisplayLauncher</string>
 
-    <!-- Force secondary home launcher specified in config_secondaryHomeComponent always. If this is
-         not set, secondary home launcher can be replaced by user. -->
-    <bool name ="config_useSystemProvidedLauncherForSecondary">false</bool>
-
     <!-- If device supports corner radius on windows.
          This should be turned off on low-end devices to improve animation performance. -->
     <bool name="config_supportsRoundedCornersOnWindows">true</bool>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index ef834aa..75fd3a0 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5373,4 +5373,7 @@
     <!-- ChooserActivity - No direct share targets are available. [CHAR LIMIT=NONE] -->
     <string name="chooser_no_direct_share_targets">Direct share not available</string>
 
+    <!-- ChooserActivity - Alphabetically sorted apps list label. [CHAR LIMIT=NONE] -->
+    <string name="chooser_all_apps_button_label">Apps list</string>
+
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 085ce56..fbe340e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3678,7 +3678,6 @@
 
   <!-- For Secondary Launcher -->
   <java-symbol type="string" name="config_secondaryHomeComponent" />
-  <java-symbol type="bool" name="config_useSystemProvidedLauncherForSecondary" />
 
   <java-symbol type="string" name="battery_saver_notification_channel_name" />
   <java-symbol type="string" name="battery_saver_sticky_disabled_notification_title" />
@@ -3772,4 +3771,6 @@
   <java-symbol type="color" name="chooser_gradient_highlight" />
   <java-symbol type="drawable" name="chooser_direct_share_label_placeholder" />
   <java-symbol type="dimen" name="chooser_direct_share_label_placeholder_max_width" />
+  <java-symbol type="layout" name="chooser_az_label_row" />
+  <java-symbol type="string" name="chooser_all_apps_button_label" />
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index d8f543f..112dde6 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -53,7 +53,7 @@
     <!-- Clock with header -->
     <dimen name="widget_small_font_size">22dp</dimen>
     <dimen name="widget_vertical_padding">32dp</dimen>
-    <dimen name="widget_vertical_padding_clock">30dp</dimen>
+    <dimen name="widget_vertical_padding_clock">12dp</dimen>
     <!-- Subtitle paddings -->
     <dimen name="widget_horizontal_padding">8dp</dimen>
     <dimen name="widget_icon_size">16dp</dimen>
diff --git a/packages/SystemUI/res/drawable/button_border_selected.xml b/packages/SystemUI/res/drawable/button_border_selected.xml
index d9299ec..1e40ade 100644
--- a/packages/SystemUI/res/drawable/button_border_selected.xml
+++ b/packages/SystemUI/res/drawable/button_border_selected.xml
@@ -20,6 +20,6 @@
         android:color="@color/notification_guts_selection_bg" />
     <stroke
         android:width="2dp"
-        android:color="?android:attr/colorAccent"/>
+        android:color="@color/GM2_grey_300"/>
     <corners android:radius="@dimen/rect_button_radius" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 58af4a2..087e0bd 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -103,7 +103,6 @@
         android:id="@+id/channel_info"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginBottom="@dimen/notification_guts_button_spacing"
         android:paddingEnd="@*android:dimen/notification_content_margin_end"
         android:gravity="center"
         android:orientation="vertical">
@@ -127,6 +126,7 @@
         android:id="@+id/blocking_helper"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/notification_guts_button_spacing"
         android:layout_marginBottom="@dimen/notification_guts_button_spacing"
         android:paddingEnd="@*android:dimen/notification_content_margin_end"
         android:clipChildren="false"
@@ -216,67 +216,107 @@
             android:id="@+id/interruptiveness_settings"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:gravity="center"
             android:orientation="vertical">
 
-            <LinearLayout
-                android:id="@+id/buttons"
+            <RelativeLayout
+                android:id="@+id/alert"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:orientation="horizontal"
-                android:gravity="center">
-
-                <Button
-                    android:id="@+id/alert"
-                    android:minWidth="@dimen/notification_importance_button_width"
+                android:padding="@dimen/notification_importance_button_padding"
+                android:clickable="true"
+                android:focusable="true">
+                <ImageView
+                    android:id="@+id/alert_icon"
+                    android:src="@drawable/ic_notification_interruptive"
+                    android:background="@android:color/transparent"
+                    android:layout_gravity="center"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:minHeight="@dimen/notification_importance_toggle_size"
-                    android:paddingStart="@dimen/notification_importance_button_horiz_padding"
-                    android:paddingEnd="@dimen/notification_importance_button_horiz_padding"
-                    android:drawablePadding="@dimen/notification_importance_drawable_padding"
-                    android:foreground="@drawable/button_ripple_radius"
-                    android:drawableLeft="@drawable/ic_notification_interruptive"
-                    android:text="@string/notification_alert_title" />
-
-                <Button
-                    android:id="@+id/silence"
-                    android:minWidth="@dimen/notification_importance_button_width"
-                    android:layout_width="wrap_content"
+                    android:clickable="false"
+                    android:focusable="false"/>
+                <TextView
+                    android:id="@+id/alert_label"
+                    android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:minHeight="@dimen/notification_importance_toggle_size"
-                    android:paddingStart="@dimen/notification_importance_button_horiz_padding"
-                    android:paddingEnd="@dimen/notification_importance_button_horiz_padding"
-                    android:drawablePadding="@dimen/notification_importance_drawable_padding"
-                    android:foreground="@drawable/button_ripple_radius"
-                    android:layout_marginStart="@dimen/notification_importance_button_separation"
-                    android:drawableLeft="@drawable/ic_notification_gentle"
-                    android:text="@string/notification_silence_title" />
-            </LinearLayout>
+                    android:ellipsize="end"
+                    android:maxLines="1"
+                    android:clickable="false"
+                    android:focusable="false"
+                    android:layout_toEndOf="@id/alert_icon"
+                    android:layout_marginStart="@dimen/notification_importance_drawable_padding"
+                    android:textAppearance="@style/TextAppearance.NotificationImportanceButton.Unselected"
+                    android:text="@string/notification_alert_title"/>
+                <TextView
+                    android:id="@+id/alert_summary"
+                    android:paddingTop="@dimen/notification_importance_button_padding"
+                    android:text="@string/notification_channel_summary_default"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:clickable="false"
+                    android:focusable="false"
+                    android:ellipsize="end"
+                    android:maxLines="2"
+                    android:layout_below="@id/alert_icon"
+                    android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/>
+            </RelativeLayout>
 
-            <TextView
-                android:id="@+id/description"
+            <RelativeLayout
+                android:id="@+id/silence"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:text="@string/notification_alert_title"
-                android:gravity="center"
-                android:layout_marginTop="@dimen/notification_importance_text_marginTop"
-                android:paddingStart="@dimen/notification_importance_description_padding"
-                android:paddingEnd="@dimen/notification_importance_description_padding"
-                android:textAppearance="@style/TextAppearance.NotificationImportanceDetail" />
+                android:padding="@dimen/notification_importance_button_padding"
+                android:layout_marginTop="@dimen/notification_importance_button_separation"
+                android:clickable="true"
+                android:focusable="true">
+                <ImageView
+                    android:id="@+id/silence_icon"
+                    android:src="@drawable/ic_notification_gentle"
+                    android:background="@android:color/transparent"
+                    android:layout_gravity="center"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:clickable="false"
+                    android:focusable="false"/>
+                <TextView
+                    android:id="@+id/silence_label"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:ellipsize="end"
+                    android:maxLines="1"
+                    android:clickable="false"
+                    android:focusable="false"
+                    android:layout_toEndOf="@id/silence_icon"
+                    android:layout_marginStart="@dimen/notification_importance_drawable_padding"
+                    android:textAppearance="@style/TextAppearance.NotificationImportanceButton.Unselected"
+                    android:text="@string/notification_silence_title"/>
+                <TextView
+                    android:id="@+id/silence_summary"
+                    android:paddingTop="@dimen/notification_importance_button_padding"
+                    android:text="@string/notification_channel_summary_default"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:clickable="false"
+                    android:focusable="false"
+                    android:ellipsize="end"
+                    android:maxLines="2"
+                    android:layout_below="@id/silence_icon"
+                    android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/>
+            </RelativeLayout>
+
         </LinearLayout>
 
         <RelativeLayout
             android:id="@+id/bottom_buttons"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:paddingTop="@dimen/notification_guts_button_spacing" >
+            android:layout_marginTop="@dimen/notification_guts_button_spacing" >
             <TextView
                 android:id="@+id/turn_off_notifications"
                 android:text="@string/inline_turn_off_notifications"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_alignParentStart="true"
-                android:layout_centerVertical="true"
                 android:minWidth="@dimen/notification_importance_toggle_size"
                 android:minHeight="@dimen/notification_importance_toggle_size"
                 android:maxWidth="200dp"
@@ -286,11 +326,11 @@
                 android:text="@string/inline_ok_button"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_centerVertical="true"
+                android:layout_alignParentEnd="true"
+                android:gravity="center_vertical|end"
                 android:maxWidth="125dp"
                 android:minWidth="@dimen/notification_importance_toggle_size"
                 android:minHeight="@dimen/notification_importance_toggle_size"
-                android:layout_alignParentEnd="true"
                 style="@style/TextAppearance.NotificationInfo.Button"/>
         </RelativeLayout>
 
diff --git a/packages/SystemUI/res/layout/quick_qs_status_icons.xml b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
index 7f69cf4..5b7e7e7 100644
--- a/packages/SystemUI/res/layout/quick_qs_status_icons.xml
+++ b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
@@ -19,8 +19,8 @@
     android:id="@+id/quick_qs_status_icons"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:layout_marginTop="@dimen/qs_header_top_margin"
-    android:layout_marginBottom="14dp"
+    android:paddingTop="@dimen/qs_header_top_padding"
+    android:paddingBottom="@dimen/qs_header_bottom_padding"
     android:paddingStart="@dimen/status_bar_padding_start"
     android:paddingEnd="@dimen/status_bar_padding_end"
     android:layout_below="@id/quick_status_bar_system_icons"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 955cfb0..ce958ab 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -212,7 +212,7 @@
     <dimen name="notification_guts_option_horizontal_padding">15dp</dimen>
 
     <!-- The vertical space between items in the alert selections in the inline settings -->
-    <dimen name="notification_guts_option_vertical_padding">24dp</dimen>
+    <dimen name="notification_guts_option_vertical_padding">20dp</dimen>
 
     <!-- The vertical space between the alert selections in the inline settings -->
     <dimen name="notification_guts_option_vertical_margin">6dp</dimen>
@@ -226,10 +226,12 @@
     <dimen name="notification_importance_button_horiz_padding">28dp</dimen>
     <dimen name="notification_importance_drawable_padding">8dp</dimen>
     <dimen name="notification_importance_description_padding">20dp</dimen>
-    <dimen name="notification_importance_description_text">12sp</dimen>
+    <dimen name="notification_importance_header_text">12sp</dimen>
+    <dimen name="notification_importance_description_text">14sp</dimen>
     <dimen name="notification_importance_channel_text">16sp</dimen>
     <dimen name="notification_importance_channel_group_text">14sp</dimen>
     <dimen name="notification_importance_button_text">16sp</dimen>
+    <dimen name="notification_importance_button_padding">14dp</dimen>
     <dimen name="rect_button_radius">8dp</dimen>
 
     <!-- The minimum height for the snackbar shown after the snooze option has been chosen. -->
@@ -492,7 +494,8 @@
     <dimen name="qs_footer_padding_end">16dp</dimen>
     <dimen name="qs_footer_icon_size">16dp</dimen>
     <dimen name="qs_paged_tile_layout_padding_bottom">0dp</dimen>
-    <dimen name="qs_header_top_margin">15dp</dimen>
+    <dimen name="qs_header_top_padding">15dp</dimen>
+    <dimen name="qs_header_bottom_padding">14dp</dimen>
 
     <dimen name="qs_notif_collapsed_space">64dp</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 60d7126..cea336c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1659,19 +1659,19 @@
     <string name="notification_alert_title">Prioritized</string>
 
     <!-- [CHAR LIMIT=150] Notification Importance title: low importance level summary -->
-    <string name="notification_channel_summary_low">Always silent. Displays in pull-down shade.</string>
+    <string name="notification_channel_summary_low">Helps you focus with notifications only in the pull-down shade. Always silent.</string>
 
     <!-- [CHAR LIMIT=150] Notification Importance title: low importance level summary -->
-    <string name="notification_channel_summary_low_status">Always silent. Displays in pull-down shade &amp; status bar.</string>
+    <string name="notification_channel_summary_low_status">Displays below priority notifications. Always silent.</string>
 
     <!-- [CHAR LIMIT=150] Notification Importance title: low importance level summary -->
-    <string name="notification_channel_summary_low_lock">Always silent. Displays in pull-down shade &amp; on lock screen.</string>
+    <string name="notification_channel_summary_low_lock">Displays below priority notifications. Always silent.</string>
 
     <!-- [CHAR LIMIT=150] Notification Importance title: low importance level summary -->
-    <string name="notification_channel_summary_low_status_lock">Always silent. Displays in pull-down shade, status bar &amp; on lock screen.</string>
+    <string name="notification_channel_summary_low_status_lock">Displays below priority notifications. Always silent.</string>
 
     <!-- [CHAR LIMIT=150] Notification Importance title: normal importance level summary -->
-    <string name="notification_channel_summary_default">Makes sound and displays in pull-down shade, status bar &amp; on lock screen.</string>
+    <string name="notification_channel_summary_default">Gets your attention with sound &amp; a status bar icon. Shows on lock screen.</string>
 
     <!-- Notification: Control panel: Label that displays when the app's notifications cannot be blocked. -->
     <string name="notification_unblockable_desc">These notifications can\'t be modified.</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index d765f0c..23de2ac 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -473,7 +473,7 @@
     </style>
 
     <style name="TextAppearance.NotificationImportanceHeader">
-        <item name="android:textSize">@dimen/notification_importance_description_text</item>
+        <item name="android:textSize">@dimen/notification_importance_header_text</item>
         <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
         <item name="android:textColor">@color/notification_guts_header_text_color</item>
     </style>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java
index 1a684a0..40d98c1 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java
@@ -295,8 +295,10 @@
                         info.serviceInfo.name);
                 PluginInfo<T> t = handleLoadPlugin(name);
                 if (t == null) continue;
-                mMainHandler.obtainMessage(mMainHandler.PLUGIN_CONNECTED, t).sendToTarget();
+
+                // add plugin before sending PLUGIN_CONNECTED message
                 mPlugins.add(t);
+                mMainHandler.obtainMessage(mMainHandler.PLUGIN_CONNECTED, t).sendToTarget();
             }
         }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 2c5fa60..b826b30 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -38,11 +38,6 @@
     void startScreenPinning(int taskId) = 1;
 
     /**
-     * Enables/disables launcher/overview interaction features {@link InteractionType}.
-     */
-    void setInteractionState(int flags) = 4;
-
-    /**
      * Notifies SystemUI that split screen has been invoked.
      */
     void onSplitScreenInvoked() = 5;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/NavigationBarCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/NavigationBarCompat.java
deleted file mode 100644
index 8d6f830..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/NavigationBarCompat.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2018 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.shared.system;
-
-import android.annotation.IntDef;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-public class NavigationBarCompat extends QuickStepContract {
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({HIT_TARGET_NONE, HIT_TARGET_BACK, HIT_TARGET_HOME, HIT_TARGET_OVERVIEW})
-    public @interface HitTarget{}
-
-    public static final int HIT_TARGET_NONE = 0;
-    public static final int HIT_TARGET_BACK = 1;
-    public static final int HIT_TARGET_HOME = 2;
-    public static final int HIT_TARGET_OVERVIEW = 3;
-    public static final int HIT_TARGET_ROTATION = 4;
-    public static final int HIT_TARGET_DEAD_ZONE = 5;
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({FLAG_DISABLE_SWIPE_UP,
-            FLAG_DISABLE_QUICK_SCRUB,
-            FLAG_SHOW_OVERVIEW_BUTTON
-    })
-    public @interface InteractionType {}
-
-    /**
-     * Interaction type: whether the gesture to swipe up from the navigation bar will trigger
-     * launcher to show overview
-     */
-    public static final int FLAG_DISABLE_SWIPE_UP = 0x1;
-    /**
-     * Interaction type: enable quick scrub interaction on the home button
-     */
-    public static final int FLAG_DISABLE_QUICK_SCRUB = 0x2;
-
-    /**
-     * Interaction type: show/hide the overview button while this service is connected to launcher
-     */
-    public static final int FLAG_SHOW_OVERVIEW_BUTTON = 0x4;
-
-
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
index 0a6885c..037a8d3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
@@ -59,14 +59,22 @@
     private ColorStateList mDefaultColorState;
     private CharSequence mMessage;
     private ColorStateList mNextMessageColorState = ColorStateList.valueOf(DEFAULT_COLOR);
+    private boolean mBouncerVisible;
 
     private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
         public void onFinishedGoingToSleep(int why) {
             setSelected(false);
-        };
+        }
+
         public void onStartedWakingUp() {
             setSelected(true);
-        };
+        }
+
+        @Override
+        public void onKeyguardBouncerChanged(boolean bouncer) {
+            mBouncerVisible = bouncer;
+            update();
+        }
     };
 
     public KeyguardMessageArea(Context context) {
@@ -188,7 +196,7 @@
 
     private void update() {
         CharSequence status = mMessage;
-        setVisibility(TextUtils.isEmpty(status) ? INVISIBLE : VISIBLE);
+        setVisibility(TextUtils.isEmpty(status) || !mBouncerVisible ? INVISIBLE : VISIBLE);
         setText(status);
         ColorStateList colorState = mDefaultColorState;
         if (mNextMessageColorState.getDefaultColor() != DEFAULT_COLOR) {
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 1feb63d..4b6306a 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -35,6 +35,7 @@
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dock.DockManager;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -296,6 +297,7 @@
     @Inject Lazy<PackageManagerWrapper> mPackageManagerWrapper;
     @Inject Lazy<SensorPrivacyController> mSensorPrivacyController;
     @Inject Lazy<DumpController> mDumpController;
+    @Inject Lazy<DockManager> mDockManager;
 
     @Inject
     public Dependency() {
@@ -470,6 +472,7 @@
         mProviders.put(PackageManagerWrapper.class, mPackageManagerWrapper::get);
         mProviders.put(SensorPrivacyController.class, mSensorPrivacyController::get);
         mProviders.put(DumpController.class, mDumpController::get);
+        mProviders.put(DockManager.class, mDockManager::get);
 
         // TODO(b/118592525): to support multi-display , we start to add something which is
         //                    per-display, while others may be global. I think it's time to add
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index 2797570..10ae343 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -57,7 +57,6 @@
             Key.SEEN_RINGER_GUIDANCE_COUNT,
             Key.QS_HAS_TURNED_OFF_MOBILE_DATA,
             Key.TOUCHED_RINGER_TOGGLE,
-            Key.QUICK_STEP_INTERACTION_FLAGS,
             Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP
     })
     public @interface Key {
@@ -103,7 +102,6 @@
         String QS_TILE_SPECS_REVEALED = "QsTileSpecsRevealed";
         String QS_HAS_TURNED_OFF_MOBILE_DATA = "QsHasTurnedOffMobileData";
         String TOUCHED_RINGER_TOGGLE = "TouchedRingerToggle";
-        String QUICK_STEP_INTERACTION_FLAGS = "QuickStepInteractionFlags";
         String HAS_SEEN_ODI_CAPTIONS_TOOLTIP = "HasSeenODICaptionsTooltip";
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index ffb5e81..f9926f3 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -34,6 +34,7 @@
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.classifier.FalsingManager;
+import com.android.systemui.dock.DockManager;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -179,6 +180,13 @@
 
     @Singleton
     @Provides
+    @Nullable
+    public DockManager provideDockManager(Context context) {
+        return null;
+    }
+
+    @Singleton
+    @Provides
     public NotificationEntryManager provideNotificationEntryManager(Context context) {
         return new NotificationEntryManager(context);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 0332477..7094d28 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -16,9 +16,12 @@
 package com.android.systemui.bubbles;
 
 
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+
 import android.os.UserHandle;
 import android.view.LayoutInflater;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
@@ -40,15 +43,24 @@
     public NotificationEntry entry;
     BubbleView iconView;
     BubbleExpandedView expandedView;
+    private long mLastUpdated;
+    private long mLastAccessed;
 
     private static String groupId(NotificationEntry entry) {
         UserHandle user = entry.notification.getUser();
-        return user.getIdentifier() + '|' + entry.notification.getPackageName();
+        return user.getIdentifier() + "|" + entry.notification.getPackageName();
+    }
+
+    /** Used in tests when no UI is required. */
+    @VisibleForTesting(visibility = PRIVATE)
+    Bubble(NotificationEntry e) {
+        this (e, null);
     }
 
     Bubble(NotificationEntry e, BubbleExpandedView.OnBubbleBlockedListener listener) {
         entry = e;
         mKey = e.key;
+        mLastUpdated = e.notification.getPostTime();
         mGroupId = groupId(e);
         mListener = listener;
     }
@@ -101,12 +113,37 @@
 
     void setEntry(NotificationEntry entry) {
         this.entry = entry;
+        mLastUpdated = entry.notification.getPostTime();
         if (mInflated) {
             iconView.update(entry);
             expandedView.update(entry);
         }
     }
 
+    public long getLastActivity() {
+        return Math.max(mLastUpdated, mLastAccessed);
+    }
+
+    /**
+     * Should be invoked whenever a Bubble is accessed (selected while expanded).
+     */
+    void markAsAccessedAt(long lastAccessedMillis) {
+        mLastAccessed = lastAccessedMillis;
+        entry.setShowInShadeWhenBubble(false);
+    }
+
+    /**
+     * @return whether bubble is from a notification associated with a foreground service.
+     */
+    public boolean isOngoing() {
+        return entry.isForegroundService();
+    }
+
+    @Override
+    public String toString() {
+        return "Bubble{" + mKey + '}';
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index ef383ad..d071363 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -41,6 +41,7 @@
 import android.os.ServiceManager;
 import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
+import android.util.Log;
 import android.view.Display;
 import android.view.IPinnedStackController;
 import android.view.IPinnedStackListener;
@@ -83,6 +84,7 @@
 public class BubbleController implements ConfigurationController.ConfigurationListener {
 
     private static final String TAG = "BubbleController";
+    private static final boolean DEBUG = true;
 
     @Retention(SOURCE)
     @IntDef({DISMISS_USER_GESTURE, DISMISS_AGED, DISMISS_TASK_FINISHED, DISMISS_BLOCKED,
@@ -203,6 +205,9 @@
 
         configurationController.addCallback(this /* configurationListener */);
 
+        mBubbleData = data;
+        mBubbleData.setListener(mBubbleDataListener);
+
         mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
         mNotificationEntryManager.addNotificationEntryListener(mEntryListener);
 
@@ -219,9 +224,6 @@
         } catch (RemoteException e) {
             e.printStackTrace();
         }
-
-        mBubbleData = data;
-        mBubbleData.setListener(mBubbleDataListener);
         mSurfaceSynchronizer = synchronizer;
 
         mBarService = IStatusBarService.Stub.asInterface(
@@ -482,7 +484,7 @@
         }
 
         @Override
-        public void onSelectionChanged(Bubble selectedBubble) {
+        public void onSelectionChanged(@Nullable Bubble selectedBubble) {
             if (mStackView != null) {
                 mStackView.setSelectedBubble(selectedBubble);
             }
@@ -506,6 +508,18 @@
         public void apply() {
             mNotificationEntryManager.updateNotifications();
             updateVisibility();
+
+            if (DEBUG) {
+                Log.d(TAG, "[BubbleData]");
+                Log.d(TAG, formatBubblesString(mBubbleData.getBubbles(),
+                        mBubbleData.getSelectedBubble()));
+
+                if (mStackView != null) {
+                    Log.d(TAG, "[BubbleStackView]");
+                    Log.d(TAG, formatBubblesString(mStackView.getBubblesOnScreen(),
+                            mStackView.getExpandedBubble()));
+                }
+            }
         }
     };
 
@@ -623,6 +637,23 @@
         entry.setShowInShadeWhenBubble(!suppressNotification);
     }
 
+    static String formatBubblesString(List<Bubble> bubbles, Bubble selected) {
+        StringBuilder sb = new StringBuilder();
+        for (Bubble bubble : bubbles) {
+            if (bubble == null) {
+                sb.append("   <null> !!!!!\n");
+            } else {
+                boolean isSelected = (bubble == selected);
+                sb.append(String.format("%s Bubble{act=%12d, ongoing=%d, key=%s}\n",
+                        ((isSelected) ? "->" : "  "),
+                        bubble.getLastActivity(),
+                        (bubble.isOngoing() ? 1 : 0),
+                        bubble.getKey()));
+            }
+        }
+        return sb.toString();
+    }
+
     /**
      * Return true if the applications with the package name is running in foreground.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 259665d..38ba91e 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -17,21 +17,26 @@
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
 
-import android.app.ActivityManager;
+import static java.util.stream.Collectors.toList;
+
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.bubbles.BubbleController.DismissReason;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 import javax.inject.Inject;
@@ -44,6 +49,15 @@
 public class BubbleData {
 
     private static final String TAG = "BubbleData";
+    private static final boolean DEBUG = false;
+
+    private static final int MAX_BUBBLES = 5;
+
+    private static final Comparator<Bubble> BUBBLES_BY_LAST_ACTIVITY_DESCENDING =
+            Comparator.comparing(Bubble::getLastActivity).reversed();
+
+    private static final Comparator<Map.Entry<String, Long>> GROUPS_BY_LAST_ACTIVITY_DESCENDING =
+            Comparator.<Map.Entry<String, Long>, Long>comparing(Map.Entry::getValue).reversed();
 
     /**
      * This interface reports changes to the state and appearance of bubbles which should be applied
@@ -83,7 +97,7 @@
         void onOrderChanged(List<Bubble> bubbles);
 
         /** Indicates the selected bubble changed. */
-        void onSelectionChanged(Bubble selectedBubble);
+        void onSelectionChanged(@Nullable Bubble selectedBubble);
 
         /**
          * The UI should transition to the given state, incorporating any pending changes during
@@ -98,16 +112,28 @@
         void apply();
     }
 
+    interface TimeSource {
+        long currentTimeMillis();
+    }
+
     private final Context mContext;
-    private final List<Bubble> mBubbles = new ArrayList<>();
+    private List<Bubble> mBubbles;
     private Bubble mSelectedBubble;
     private boolean mExpanded;
+
+    // TODO: ensure this is invalidated at the appropriate time
+    private int mSelectedBubbleExpandedPosition = -1;
+
+    private TimeSource mTimeSource = System::currentTimeMillis;
+
+    @Nullable
     private Listener mListener;
 
     @VisibleForTesting
     @Inject
     public BubbleData(Context context) {
         mContext = context;
+        mBubbles = new ArrayList<>();
     }
 
     public boolean hasBubbles() {
@@ -122,29 +148,41 @@
         return getBubbleWithKey(key) != null;
     }
 
+    @Nullable
+    public Bubble getSelectedBubble() {
+        return mSelectedBubble;
+    }
+
     public void setExpanded(boolean expanded) {
         if (setExpandedInternal(expanded)) {
-            mListener.apply();
+            dispatchApply();
         }
     }
 
     public void setSelectedBubble(Bubble bubble) {
+        if (DEBUG) {
+            Log.d(TAG, "setSelectedBubble: " + bubble);
+        }
         if (setSelectedBubbleInternal(bubble)) {
-            mListener.apply();
+            dispatchApply();
         }
     }
 
     public void notificationEntryUpdated(NotificationEntry entry) {
+        if (DEBUG) {
+            Log.d(TAG, "notificationEntryUpdated: " + entry);
+        }
         Bubble bubble = getBubbleWithKey(entry.key);
         if (bubble == null) {
             // Create a new bubble
             bubble = new Bubble(entry, this::onBubbleBlocked);
-            mBubbles.add(0, bubble); // TODO: reorder/group
-            mListener.onBubbleAdded(bubble);
+            doAdd(bubble);
+            dispatchOnBubbleAdded(bubble);
         } else {
             // Updates an existing bubble
             bubble.setEntry(entry);
-            mListener.onBubbleUpdated(bubble);
+            doUpdate(bubble);
+            dispatchOnBubbleUpdated(bubble);
         }
         if (shouldAutoExpand(entry)) {
             setSelectedBubbleInternal(bubble);
@@ -154,49 +192,148 @@
         } else if (mSelectedBubble == null) {
             setSelectedBubbleInternal(bubble);
         }
-        // TODO: reorder/group
-        mListener.apply();
+        dispatchApply();
+    }
+
+    private void doAdd(Bubble bubble) {
+        if (DEBUG) {
+            Log.d(TAG, "doAdd: " + bubble);
+        }
+        int minInsertPoint = 0;
+        boolean newGroup = !hasBubbleWithGroupId(bubble.getGroupId());
+        if (isExpanded()) {
+            // first bubble of a group goes to the end, otherwise it goes within the existing group
+            minInsertPoint =
+                    newGroup ? mBubbles.size() : findFirstIndexForGroup(bubble.getGroupId());
+        }
+        insertBubble(minInsertPoint, bubble);
+        if (!isExpanded()) {
+            packGroup(findFirstIndexForGroup(bubble.getGroupId()));
+        }
+        if (mBubbles.size() > MAX_BUBBLES) {
+            mBubbles.stream()
+                    // sort oldest first (ascending lastActivity)
+                    .sorted(Comparator.comparingLong(Bubble::getLastActivity))
+                    // skip the selected bubble
+                    .filter((b) -> !b.equals(mSelectedBubble))
+                    .findFirst()
+                    .ifPresent((b) -> {
+                        doRemove(b.getKey(), BubbleController.DISMISS_AGED);
+                        dispatchApply();
+                    });
+        }
+    }
+
+    private void doUpdate(Bubble bubble) {
+        if (DEBUG) {
+            Log.d(TAG, "doUpdate: " + bubble);
+        }
+        if (!isExpanded()) {
+            // while collapsed, update causes re-sort
+            mBubbles.remove(bubble);
+            insertBubble(0, bubble);
+            packGroup(findFirstIndexForGroup(bubble.getGroupId()));
+        }
     }
 
     public void notificationEntryRemoved(NotificationEntry entry, @DismissReason int reason) {
-        int indexToRemove = indexForKey(entry.key);
-        if (indexToRemove >= 0) {
-            Bubble removed = mBubbles.remove(indexToRemove);
-            removed.setDismissed();
-            mListener.onBubbleRemoved(removed, reason);
-            maybeSendDeleteIntent(reason, removed.entry);
+        if (DEBUG) {
+            Log.d(TAG, "notificationEntryRemoved: entry=" + entry + " reason=" + reason);
+        }
+        doRemove(entry.key, reason);
+        dispatchApply();
+    }
 
-            if (mBubbles.isEmpty()) {
+    private void doRemove(String key, @DismissReason int reason) {
+        int indexToRemove = indexForKey(key);
+        if (indexToRemove >= 0) {
+            Bubble bubbleToRemove = mBubbles.get(indexToRemove);
+            if (mBubbles.size() == 1) {
+                // Going to become empty, handle specially.
                 setExpandedInternal(false);
                 setSelectedBubbleInternal(null);
-            } else if (removed == mSelectedBubble) {
+            }
+            mBubbles.remove(indexToRemove);
+            dispatchOnBubbleRemoved(bubbleToRemove, reason);
+
+            // Note: If mBubbles.isEmpty(), then mSelectedBubble is now null.
+            if (Objects.equals(mSelectedBubble, bubbleToRemove)) {
+                // Move selection to the new bubble at the same position.
                 int newIndex = Math.min(indexToRemove, mBubbles.size() - 1);
                 Bubble newSelected = mBubbles.get(newIndex);
                 setSelectedBubbleInternal(newSelected);
             }
-            // TODO: reorder/group
-            mListener.apply();
+            bubbleToRemove.setDismissed();
+            maybeSendDeleteIntent(reason, bubbleToRemove.entry);
         }
     }
 
     public void dismissAll(@DismissReason int reason) {
-        boolean changed = setExpandedInternal(false);
+        if (DEBUG) {
+            Log.d(TAG, "dismissAll: reason=" + reason);
+        }
+        if (mBubbles.isEmpty()) {
+            return;
+        }
+        setExpandedInternal(false);
+        setSelectedBubbleInternal(null);
         while (!mBubbles.isEmpty()) {
             Bubble bubble = mBubbles.remove(0);
             bubble.setDismissed();
             maybeSendDeleteIntent(reason, bubble.entry);
-            mListener.onBubbleRemoved(bubble, reason);
-            changed = true;
+            dispatchOnBubbleRemoved(bubble, reason);
         }
-        if (setSelectedBubbleInternal(null)) {
-            changed = true;
-        }
-        if (changed) {
-            // TODO: reorder/group
+        dispatchApply();
+    }
+
+    private void dispatchApply() {
+        if (mListener != null) {
             mListener.apply();
         }
     }
 
+    private void dispatchOnBubbleAdded(Bubble bubble) {
+        if (mListener != null) {
+            mListener.onBubbleAdded(bubble);
+        }
+    }
+
+    private void dispatchOnBubbleRemoved(Bubble bubble, @DismissReason int reason) {
+        if (mListener != null) {
+            mListener.onBubbleRemoved(bubble, reason);
+        }
+    }
+
+    private void dispatchOnExpandedChanged(boolean expanded) {
+        if (mListener != null) {
+            mListener.onExpandedChanged(expanded);
+        }
+    }
+
+    private void dispatchOnSelectionChanged(@Nullable Bubble bubble) {
+        if (mListener != null) {
+            mListener.onSelectionChanged(bubble);
+        }
+    }
+
+    private void dispatchOnBubbleUpdated(Bubble bubble) {
+        if (mListener != null) {
+            mListener.onBubbleUpdated(bubble);
+        }
+    }
+
+    private void dispatchOnOrderChanged(List<Bubble> bubbles) {
+        if (mListener != null) {
+            mListener.onOrderChanged(bubbles);
+        }
+    }
+
+    private void dispatchShowFlyoutText(Bubble bubble, String text) {
+        if (mListener != null) {
+            mListener.showFlyoutText(bubble, text);
+        }
+    }
+
     /**
      * Requests a change to the selected bubble. Calls {@link Listener#onSelectionChanged} if
      * the value changes.
@@ -204,7 +341,10 @@
      * @param bubble the new selected bubble
      * @return true if the state changed as a result
      */
-    private boolean setSelectedBubbleInternal(Bubble bubble) {
+    private boolean setSelectedBubbleInternal(@Nullable Bubble bubble) {
+        if (DEBUG) {
+            Log.d(TAG, "setSelectedBubbleInternal: " + bubble);
+        }
         if (Objects.equals(bubble, mSelectedBubble)) {
             return false;
         }
@@ -213,16 +353,17 @@
                     + " (" + bubble + ") bubbles=" + mBubbles);
             return false;
         }
-        if (mExpanded) {
-            // TODO: bubble.markAsActive() ?
-            bubble.entry.setShowInShadeWhenBubble(false);
+        if (mExpanded && bubble != null) {
+            mSelectedBubble.markAsAccessedAt(mTimeSource.currentTimeMillis());
         }
         mSelectedBubble = bubble;
-        mListener.onSelectionChanged(mSelectedBubble);
+        dispatchOnSelectionChanged(mSelectedBubble);
+        if (!mExpanded || mSelectedBubble == null) {
+            mSelectedBubbleExpandedPosition = -1;
+        }
         return true;
     }
 
-
     /**
      * Requests a change to the expanded state. Calls {@link Listener#onExpandedChanged} if
      * the value changes.
@@ -231,9 +372,15 @@
      * @return true if the state changed as a result
      */
     private boolean setExpandedInternal(boolean shouldExpand) {
+        if (DEBUG) {
+            Log.d(TAG, "setExpandedInternal: shouldExpand=" + shouldExpand);
+        }
         if (mExpanded == shouldExpand) {
             return false;
         }
+        if (mSelectedBubble != null) {
+            mSelectedBubble.markAsAccessedAt(mTimeSource.currentTimeMillis());
+        }
         if (shouldExpand) {
             if (mBubbles.isEmpty()) {
                 Log.e(TAG, "Attempt to expand stack when empty!");
@@ -243,15 +390,126 @@
                 Log.e(TAG, "Attempt to expand stack without selected bubble!");
                 return false;
             }
-            // TODO: bubble.markAsActive() ?
-            mSelectedBubble.entry.setShowInShadeWhenBubble(false);
+        } else {
+            repackAll();
         }
-        // TODO: reorder/regroup
         mExpanded = shouldExpand;
-        mListener.onExpandedChanged(mExpanded);
+        dispatchOnExpandedChanged(mExpanded);
         return true;
     }
 
+    private static long sortKey(Bubble bubble) {
+        long key = bubble.getLastActivity();
+        if (bubble.isOngoing()) {
+            // Set 2nd highest bit (signed long int), to partition between ongoing and regular
+            key |= 0x4000000000000000L;
+        }
+        return key;
+    }
+
+    /**
+     * Locates and inserts the bubble into a sorted position. The is inserted
+     * based on sort key, groupId is not considered. A call to {@link #packGroup(int)} may be
+     * required to keep grouping intact.
+     *
+     * @param minPosition the first insert point to consider
+     * @param newBubble the bubble to insert
+     * @return the position where the bubble was inserted
+     */
+    private int insertBubble(int minPosition, Bubble newBubble) {
+        long newBubbleSortKey = sortKey(newBubble);
+        String previousGroupId = null;
+
+        for (int pos = minPosition; pos < mBubbles.size(); pos++) {
+            Bubble bubbleAtPos = mBubbles.get(pos);
+            String groupIdAtPos = bubbleAtPos.getGroupId();
+            boolean atStartOfGroup = !groupIdAtPos.equals(previousGroupId);
+
+            if (atStartOfGroup && newBubbleSortKey > sortKey(bubbleAtPos)) {
+                // Insert before the start of first group which has older bubbles.
+                mBubbles.add(pos, newBubble);
+                return pos;
+            }
+            previousGroupId = groupIdAtPos;
+        }
+        mBubbles.add(newBubble);
+        return mBubbles.size() - 1;
+    }
+
+    private boolean hasBubbleWithGroupId(String groupId) {
+        return mBubbles.stream().anyMatch(b -> b.getGroupId().equals(groupId));
+    }
+
+    private int findFirstIndexForGroup(String appId) {
+        for (int i = 0; i < mBubbles.size(); i++) {
+            Bubble bubbleAtPos = mBubbles.get(i);
+            if (bubbleAtPos.getGroupId().equals(appId)) {
+                return i;
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Starting at the given position, moves all bubbles with the same group id to follow. Bubbles
+     * at positions lower than {@code position} are unchanged. Relative order within the group
+     * unchanged. Relative order of any other bubbles are also unchanged.
+     *
+     * @param position the position of the first bubble for the group
+     */
+    private void packGroup(int position) {
+        if (DEBUG) {
+            Log.d(TAG, "packGroup: position=" + position);
+        }
+        Bubble groupStart = mBubbles.get(position);
+        final String groupAppId = groupStart.getGroupId();
+        List<Bubble> moving = new ArrayList<>();
+
+        // Walk backward, collect bubbles within the group
+        for (int i = mBubbles.size() - 1; i > position; i--) {
+            if (mBubbles.get(i).getGroupId().equals(groupAppId)) {
+                moving.add(0, mBubbles.get(i));
+            }
+        }
+        mBubbles.removeAll(moving);
+        mBubbles.addAll(position + 1, moving);
+    }
+
+    private void repackAll() {
+        if (DEBUG) {
+            Log.d(TAG, "repackAll()");
+        }
+        if (mBubbles.isEmpty()) {
+            return;
+        }
+        Map<String, Long> groupLastActivity = new HashMap<>();
+        for (Bubble bubble : mBubbles) {
+            long maxSortKeyForGroup = groupLastActivity.getOrDefault(bubble.getGroupId(), 0L);
+            long sortKeyForBubble = sortKey(bubble);
+            if (sortKeyForBubble > maxSortKeyForGroup) {
+                groupLastActivity.put(bubble.getGroupId(), sortKeyForBubble);
+            }
+        }
+
+        // Sort groups by their most recently active bubble
+        List<String> groupsByMostRecentActivity =
+                groupLastActivity.entrySet().stream()
+                        .sorted(GROUPS_BY_LAST_ACTIVITY_DESCENDING)
+                        .map(Map.Entry::getKey)
+                        .collect(toList());
+
+        List<Bubble> repacked = new ArrayList<>(mBubbles.size());
+
+        // For each group, add bubbles, freshest to oldest
+        for (String appId : groupsByMostRecentActivity) {
+            mBubbles.stream()
+                    .filter((b) -> b.getGroupId().equals(appId))
+                    .sorted(BUBBLES_BY_LAST_ACTIVITY_DESCENDING)
+                    .forEachOrdered(repacked::add);
+        }
+        mBubbles = repacked;
+    }
+
     private void maybeSendDeleteIntent(@DismissReason int reason, NotificationEntry entry) {
         if (reason == BubbleController.DISMISS_USER_GESTURE) {
             Notification.BubbleMetadata bubbleMetadata = entry.getBubbleMetadata();
@@ -275,13 +533,14 @@
             Bubble bubble = i.next();
             if (bubble.getPackageName().equals(blockedPackage)) {
                 i.remove();
-                mListener.onBubbleRemoved(bubble, BubbleController.DISMISS_BLOCKED);
+                // TODO: handle removal of selected bubble, and collapse safely if emptied (see
+                //  dismissAll)
+                dispatchOnBubbleRemoved(bubble, BubbleController.DISMISS_BLOCKED);
                 changed = true;
             }
         }
         if (changed) {
-            // TODO: reorder/group
-            mListener.apply();
+            dispatchApply();
         }
     }
 
@@ -295,24 +554,11 @@
         return -1;
     }
 
-    private Bubble removeBubbleWithKey(String key) {
-        for (int i = 0; i < mBubbles.size(); i++) {
-            Bubble bubble = mBubbles.get(i);
-            if (bubble.getKey().equals(key)) {
-                mBubbles.remove(i);
-                return bubble;
-            }
-        }
-        return null;
-    }
-
     /**
      * The set of bubbles.
-     *
-     * @deprecated
      */
-    @Deprecated
-    public Collection<Bubble> getBubbles() {
+    @VisibleForTesting(visibility = PRIVATE)
+    public List<Bubble> getBubbles() {
         return Collections.unmodifiableList(mBubbles);
     }
 
@@ -327,6 +573,11 @@
         return null;
     }
 
+    @VisibleForTesting(visibility = PRIVATE)
+    void setTimeSource(TimeSource timeSource) {
+        mTimeSource = timeSource;
+    }
+
     public void setListener(Listener listener) {
         mListener = listener;
     }
@@ -334,17 +585,6 @@
     boolean shouldAutoExpand(NotificationEntry entry) {
         Notification.BubbleMetadata metadata = entry.getBubbleMetadata();
         return metadata != null && metadata.getAutoExpandBubble()
-                && isForegroundApp(entry.notification.getPackageName());
-    }
-
-    /**
-     * Return true if the applications with the package name is running in foreground.
-     *
-     * @param pkgName application package name.
-     */
-    boolean isForegroundApp(String pkgName) {
-        ActivityManager am = mContext.getSystemService(ActivityManager.class);
-        List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1 /* maxNum */);
-        return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
+                && BubbleController.isForegroundApp(mContext, entry.notification.getPackageName());
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 7029931..d1bc9a9 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -66,6 +66,7 @@
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
@@ -227,7 +228,7 @@
 
         mBubbleData = data;
         mInflater = LayoutInflater.from(context);
-        mTouchHandler = new BubbleTouchHandler(context, this);
+        mTouchHandler = new BubbleTouchHandler(this, data, context);
         setOnTouchListener(mTouchHandler);
         mInflater = LayoutInflater.from(context);
 
@@ -503,6 +504,9 @@
 
     // via BubbleData.Listener
     void addBubble(Bubble bubble) {
+        if (DEBUG) {
+            Log.d(TAG, "addBubble: " + bubble);
+        }
         bubble.inflate(mInflater, this);
         mBubbleContainer.addView(bubble.iconView, 0,
                 new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
@@ -513,10 +517,17 @@
 
     // via BubbleData.Listener
     void removeBubble(Bubble bubble) {
+        if (DEBUG) {
+            Log.d(TAG, "removeBubble: " + bubble);
+        }
         // Remove it from the views
         int removedIndex = mBubbleContainer.indexOfChild(bubble.iconView);
-        mBubbleContainer.removeViewAt(removedIndex);
-        logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
+        if (removedIndex >= 0) {
+            mBubbleContainer.removeViewAt(removedIndex);
+            logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
+        } else {
+            Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
+        }
     }
 
     // via BubbleData.Listener
@@ -531,7 +542,10 @@
      * position of any bubble.
      */
     // via BubbleData.Listener
-    public void setSelectedBubble(Bubble bubbleToSelect) {
+    public void setSelectedBubble(@Nullable Bubble bubbleToSelect) {
+        if (DEBUG) {
+            Log.d(TAG, "setSelectedBubble: " + bubbleToSelect);
+        }
         if (mExpandedBubble != null && mExpandedBubble.equals(bubbleToSelect)) {
             return;
         }
@@ -562,6 +576,9 @@
      */
     // via BubbleData.Listener
     public void setExpanded(boolean shouldExpand) {
+        if (DEBUG) {
+            Log.d(TAG, "setExpanded: " + shouldExpand);
+        }
         boolean wasExpanded = mIsExpanded;
         if (shouldExpand == wasExpanded) {
             return;
@@ -586,6 +603,9 @@
      */
     @Deprecated
     void stackDismissed(int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "stackDismissed: reason=" + reason);
+        }
         mBubbleData.dismissAll(reason);
         logBubbleEvent(null /* no bubble associated with bubble stack dismiss */,
                 StatsLog.BUBBLE_UICHANGED__ACTION__STACK_DISMISSED);
@@ -633,6 +653,9 @@
     @Deprecated
     @MainThread
     void collapseStack() {
+        if (DEBUG) {
+            Log.d(TAG, "collapseStack()");
+        }
         mBubbleData.setExpanded(false);
     }
 
@@ -642,6 +665,9 @@
     @Deprecated
     @MainThread
     void collapseStack(Runnable endRunnable) {
+        if (DEBUG) {
+            Log.d(TAG, "collapseStack(endRunnable)");
+        }
         collapseStack();
         // TODO - use the runnable at end of animation
         endRunnable.run();
@@ -657,6 +683,9 @@
     @Deprecated
     @MainThread
     void expandStack() {
+        if (DEBUG) {
+            Log.d(TAG, "expandStack()");
+        }
         mBubbleData.setExpanded(true);
     }
 
@@ -664,6 +693,9 @@
      * Tell the stack to animate to collapsed or expanded state.
      */
     private void animateExpansion(boolean shouldExpand) {
+        if (DEBUG) {
+            Log.d(TAG, "animateExpansion: shouldExpand=" + shouldExpand);
+        }
         if (mIsExpanded != shouldExpand) {
             hideFlyoutImmediate();
 
@@ -745,6 +777,9 @@
 
     /** Called when a drag operation on an individual bubble has started. */
     public void onBubbleDragStart(View bubble) {
+        if (DEBUG) {
+            Log.d(TAG, "onBubbleDragStart: bubble=" + bubble);
+        }
         mExpandedAnimationController.prepareForBubbleDrag(bubble);
     }
 
@@ -760,6 +795,9 @@
     /** Called when a drag operation on an individual bubble has finished. */
     public void onBubbleDragFinish(
             View bubble, float x, float y, float velX, float velY, boolean dismissed) {
+        if (DEBUG) {
+            Log.d(TAG, "onBubbleDragFinish: bubble=" + bubble + ", dismissed=" + dismissed);
+        }
         if (!mIsExpanded || mIsExpansionAnimating) {
             return;
         }
@@ -772,6 +810,9 @@
     }
 
     void onDragStart() {
+        if (DEBUG) {
+            Log.d(TAG, "onDragStart()");
+        }
         if (mIsExpanded || mIsExpansionAnimating) {
             return;
         }
@@ -792,6 +833,9 @@
     }
 
     void onDragFinish(float x, float y, float velX, float velY) {
+        if (DEBUG) {
+            Log.d(TAG, "onDragFinish");
+        }
         // TODO: Add fling to bottom to dismiss.
         mIsDragging = false;
 
@@ -958,6 +1002,9 @@
     }
 
     private void updateExpandedBubble() {
+        if (DEBUG) {
+            Log.d(TAG, "updateExpandedBubble()");
+        }
         mExpandedViewContainer.removeAllViews();
         if (mExpandedBubble != null && mIsExpanded) {
             mExpandedViewContainer.addView(mExpandedBubble.expandedView);
@@ -1036,7 +1083,9 @@
     }
 
     private void applyCurrentState() {
-        Log.d(TAG, "applyCurrentState: mIsExpanded=" + mIsExpanded);
+        if (DEBUG) {
+            Log.d(TAG, "applyCurrentState: mIsExpanded=" + mIsExpanded);
+        }
         mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
         if (mIsExpanded) {
             // First update the view so that it calculates a new height (ensuring the y position
@@ -1075,10 +1124,14 @@
     }
 
     private void updatePointerPosition() {
-        if (mExpandedBubble != null) {
-            float pointerPosition = mExpandedBubble.iconView.getTranslationX()
-                    + (mExpandedBubble.iconView.getWidth() / 2f);
-            mExpandedBubble.expandedView.setPointerPosition((int) pointerPosition);
+        if (DEBUG) {
+            Log.d(TAG, "updatePointerPosition()");
+        }
+        Bubble expandedBubble = getExpandedBubble();
+        if (expandedBubble != null) {
+            BubbleView iconView = expandedBubble.iconView;
+            float pointerPosition = iconView.getTranslationX() + (iconView.getWidth() / 2f);
+            expandedBubble.expandedView.setPointerPosition((int) pointerPosition);
         }
     }
 
@@ -1174,4 +1227,18 @@
         }
         return mExpandedBubble.expandedView.performBackPressIfNeeded();
     }
+
+    /** For debugging only */
+    List<Bubble> getBubblesOnScreen() {
+        List<Bubble> bubbles = new ArrayList<>();
+        for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
+            View child = mBubbleContainer.getChildAt(i);
+            if (child instanceof BubbleView) {
+                String key = ((BubbleView) child).getKey();
+                Bubble bubble = mBubbleData.getBubbleWithKey(key);
+                bubbles.add(bubble);
+            }
+        }
+        return bubbles;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index a51d46c..82e6279 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -37,9 +37,12 @@
     /** Velocity required to dismiss a bubble without dragging it into the dismiss target. */
     private static final float DISMISS_MIN_VELOCITY = 4000f;
 
+    private static final String TAG = "BubbleTouchHandler";
+
     private final PointF mTouchDown = new PointF();
     private final PointF mViewPositionOnTouchDown = new PointF();
     private final BubbleStackView mStack;
+    private final BubbleData mBubbleData;
 
     private BubbleController mController = Dependency.get(BubbleController.class);
     private PipDismissViewController mDismissViewController;
@@ -60,10 +63,12 @@
     /** View that was initially touched, when we received the first ACTION_DOWN event. */
     private View mTouchedView;
 
-    BubbleTouchHandler(Context context, BubbleStackView stackView) {
+    BubbleTouchHandler(BubbleStackView stackView,
+            BubbleData bubbleData, Context context) {
         final int touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
         mTouchSlopSquared = touchSlop * touchSlop;
         mDismissViewController = new PipDismissViewController(context);
+        mBubbleData = bubbleData;
         mStack = stackView;
     }
 
@@ -80,7 +85,7 @@
         // If this is an ACTION_OUTSIDE event, or the stack reported that we aren't touching
         // anything, collapse the stack.
         if (action == MotionEvent.ACTION_OUTSIDE || mTouchedView == null) {
-            mStack.collapseStack();
+            mBubbleData.setExpanded(false);
             resetForNextGesture();
             return false;
         }
@@ -151,8 +156,8 @@
                     mStack.onDragFinishAsDismiss();
                 } else if (isFlyout) {
                     // TODO(b/129768381): Expand if tapped, dismiss if swiped away.
-                    if (!mStack.isExpanded() && !mMovedEnough) {
-                        mStack.expandStack();
+                    if (!mBubbleData.isExpanded() && !mMovedEnough) {
+                        mBubbleData.setExpanded(true);
                     }
                 } else if (mMovedEnough) {
                     mVelocityTracker.computeCurrentVelocity(/* maxVelocity */ 1000);
@@ -170,15 +175,13 @@
                         }
                     }
                 } else if (mTouchedView == mStack.getExpandedBubbleView()) {
-                    mStack.collapseStack();
+                    mBubbleData.setExpanded(false);
                 } else if (isStack) {
-                    if (mStack.isExpanded()) {
-                        mStack.collapseStack();
-                    } else {
-                        mStack.expandStack();
-                    }
+                    // Toggle expansion
+                    mBubbleData.setExpanded(!mBubbleData.isExpanded());
                 } else {
-                    mStack.setExpandedBubble(((BubbleView) mTouchedView).getKey());
+                    final String key = ((BubbleView) mTouchedView).getKey();
+                    mBubbleData.setSelectedBubble(mBubbleData.getBubbleWithKey(key));
                 }
 
                 resetForNextGesture();
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index 0607654..5196ec6 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -27,7 +27,6 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.SystemUIApplication;
 import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.dock.DockManager;
@@ -46,7 +45,7 @@
         Context context = dozeService;
         SensorManager sensorManager = Dependency.get(AsyncSensorManager.class);
         AlarmManager alarmManager = context.getSystemService(AlarmManager.class);
-        DockManager dockManager = SysUiServiceProvider.getComponent(context, DockManager.class);
+        DockManager dockManager = Dependency.get(DockManager.class);
 
         DozeHost host = getHost(dozeService);
         AmbientDisplayConfiguration config = new AmbientDisplayConfiguration(context);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
index 89ecc6a..d50f294e4 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
@@ -43,7 +43,7 @@
  */
 public class PipNotification {
     private static final String TAG = "PipNotification";
-    private static final String NOTIFICATION_TAG = PipNotification.class.getName();
+    private static final String NOTIFICATION_TAG = PipNotification.class.getSimpleName();
     private static final boolean DEBUG = PipManager.DEBUG;
 
     private static final String ACTION_MENU = "PipNotification.menu";
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 6adce83..9431f20 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -38,6 +38,7 @@
 import android.service.notification.ZenModeConfig;
 import android.text.format.DateUtils;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.Pair;
 import android.util.StatsLog;
 import android.view.ContextThemeWrapper;
@@ -524,11 +525,11 @@
             mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
                     AlarmClock.ACTION_SHOW_ALARMS), 0);
         } else if (v == mNextAlarmContainer) {
-            if (mNextAlarm.getShowIntent() != null
-                    && mNextAlarm.getShowIntent().getIntent() != null) {
+            if (mNextAlarm.getShowIntent() != null) {
                 mActivityStarter.postStartActivityDismissingKeyguard(
-                        mNextAlarm.getShowIntent().getIntent(), 0);
+                        mNextAlarm.getShowIntent());
             } else {
+                Log.d(TAG, "No PendingIntent for next alarm. Using default intent");
                 mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
                         AlarmClock.ACTION_SHOW_ALARMS), 0);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 5ed6a42..4fb4999 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -22,9 +22,6 @@
 import static android.view.MotionEvent.ACTION_UP;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 
-import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_SWIPE_UP;
-import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON;
-import static com.android.systemui.shared.system.NavigationBarCompat.InteractionType;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
@@ -61,7 +58,6 @@
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
-import com.android.systemui.Prefs;
 import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
 import com.android.systemui.shared.recents.IOverviewProxy;
@@ -103,10 +99,6 @@
     // Max backoff caps at 5 mins
     private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
 
-    // Default interaction flags if swipe up is disabled before connecting to launcher
-    private static final int DEFAULT_DISABLE_SWIPE_UP_STATE = FLAG_DISABLE_SWIPE_UP
-            | FLAG_SHOW_OVERVIEW_BUTTON;
-
     private final Context mContext;
     private final Handler mHandler;
     private final Runnable mConnectionRunnable = this::internalConnectToCurrentUser;
@@ -119,7 +111,6 @@
 
     private IOverviewProxy mOverviewProxy;
     private int mConnectionBackoffAttempts;
-    private @InteractionType int mInteractionFlags;
     private @SystemUiStateFlags int mSysUiStateFlags;
     private boolean mBound;
     private boolean mIsEnabled;
@@ -233,27 +224,6 @@
         }
 
         @Override
-        public void setInteractionState(@InteractionType int flags) {
-            if (!verifyCaller("setInteractionState")) {
-                return;
-            }
-            long token = Binder.clearCallingIdentity();
-            try {
-                if (mInteractionFlags != flags) {
-                    mInteractionFlags = flags;
-                    mHandler.post(() -> {
-                        for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
-                            mConnectionCallbacks.get(i).onInteractionFlagsChanged(flags);
-                        }
-                    });
-                }
-            } finally {
-                Prefs.putInt(mContext, Prefs.Key.QUICK_STEP_INTERACTION_FLAGS, mInteractionFlags);
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override
         public Rect getNonMinimizedSplitScreenSecondaryBounds() {
             if (!verifyCaller("getNonMinimizedSplitScreenSecondaryBounds")) {
                 return null;
@@ -380,12 +350,6 @@
         public void onReceive(Context context, Intent intent) {
             updateEnabledState();
 
-            // When launcher service is disabled, reset interaction flags because it is inactive
-            if (!isEnabled()) {
-                mInteractionFlags = getDefaultInteractionFlags();
-                Prefs.remove(mContext, Prefs.Key.QUICK_STEP_INTERACTION_FLAGS);
-            }
-
             // Reconnect immediately, instead of waiting for resume to arrive.
             startConnectionToCurrentUser();
         }
@@ -479,8 +443,6 @@
                 com.android.internal.R.string.config_recentsComponentName));
         mQuickStepIntent = new Intent(ACTION_QUICKSTEP)
                 .setPackage(mRecentsComponentName.getPackageName());
-        mInteractionFlags = Prefs.getInt(mContext, Prefs.Key.QUICK_STEP_INTERACTION_FLAGS,
-                getDefaultInteractionFlags());
         mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext.getResources());
         mSupportsRoundedCornersOnWindows = ScreenDecorationsUtils
                 .supportsRoundedCornersOnWindows(mContext.getResources());
@@ -653,7 +615,6 @@
     public void addCallback(OverviewProxyListener listener) {
         mConnectionCallbacks.add(listener);
         listener.onConnectionChanged(mOverviewProxy != null);
-        listener.onInteractionFlagsChanged(mInteractionFlags);
         listener.onBackButtonAlphaChanged(mBackButtonAlpha, false);
     }
 
@@ -663,7 +624,7 @@
     }
 
     public boolean shouldShowSwipeUpUI() {
-        return isEnabled() && ((mInteractionFlags & FLAG_DISABLE_SWIPE_UP) == 0);
+        return isEnabled() && !QuickStepContract.isLegacyMode(mNavBarMode);
     }
 
     public boolean isEnabled() {
@@ -674,10 +635,6 @@
         return mOverviewProxy;
     }
 
-    public int getInteractionFlags() {
-        return mInteractionFlags;
-    }
-
     private void disconnectFromLauncherService() {
         if (mBound) {
             // Always unbind the service (ie. if called through onNullBinding or onBindingDied)
@@ -693,13 +650,6 @@
         }
     }
 
-    private int getDefaultInteractionFlags() {
-        // If there is no settings available use device default or get it from settings
-        return QuickStepContract.isLegacyMode(mNavBarMode)
-                ? DEFAULT_DISABLE_SWIPE_UP_STATE
-                : 0;
-    }
-
     private void notifyBackButtonAlphaChanged(float alpha, boolean animate) {
         for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
             mConnectionCallbacks.get(i).onBackButtonAlphaChanged(alpha, animate);
@@ -765,7 +715,6 @@
         pw.print("  isCurrentUserSetup="); pw.println(mDeviceProvisionedController
                 .isCurrentUserSetup());
         pw.print("  connectionBackoffAttempts="); pw.println(mConnectionBackoffAttempts);
-        pw.print("  interactionFlags="); pw.println(mInteractionFlags);
 
         pw.print("  quickStepIntent="); pw.println(mQuickStepIntent);
         pw.print("  quickStepIntentResolved="); pw.println(isEnabled());
@@ -775,7 +724,6 @@
     public interface OverviewProxyListener {
         default void onConnectionChanged(boolean isConnected) {}
         default void onQuickStepStarted() {}
-        default void onInteractionFlagsChanged(@InteractionType int flags) {}
         default void onOverviewShown(boolean fromHome) {}
         default void onQuickScrubStarted() {}
         default void onBackButtonAlphaChanged(float alpha, boolean animate) {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
index 85848ca..bd25209 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
@@ -53,7 +53,7 @@
 @Singleton
 public class NavigationBarController implements Callbacks {
 
-    private static final String TAG = NavigationBarController.class.getName();
+    private static final String TAG = NavigationBarController.class.getSimpleName();
 
     private final Context mContext;
     private final Handler mHandler;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index 1f8ca37..d49f168 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -40,8 +40,11 @@
 import android.metrics.LogMaker;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
+import android.transition.AutoTransition;
+import android.transition.TransitionManager;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
@@ -88,6 +91,8 @@
     // standard controls
     private static final int ACTION_ALERT = 5;
 
+    private static final int BUTTON_ANIM_TIME_MS = 200;
+
     private INotificationManager mINotificationManager;
     private PackageManager mPm;
     private MetricsLogger mMetricsLogger;
@@ -102,6 +107,8 @@
     private boolean mWasShownHighPriority;
     private boolean mShowOnLockscreen;
     private boolean mShowInStatusBar;
+    private boolean mPressedApply;
+
     /**
      * The last importance level chosen by the user.  Null if the user has not chosen an importance
      * level; non-null once the user takes an action which indicates an explicit preference.
@@ -132,7 +139,7 @@
     private OnClickListener mOnAlert = v -> {
         mExitReason = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING;
         mChosenImportance = IMPORTANCE_DEFAULT;
-        setImportanceSummary(ACTION_ALERT);
+        setImportanceSummary(ACTION_ALERT, true);
         updateButtons(ACTION_ALERT);
     };
 
@@ -140,12 +147,13 @@
     private OnClickListener mOnSilent = v -> {
         mExitReason = NotificationCounters.BLOCKING_HELPER_DELIVER_SILENTLY;
         mChosenImportance = IMPORTANCE_LOW;
-        setImportanceSummary(ACTION_TOGGLE_SILENT);
+        setImportanceSummary(ACTION_TOGGLE_SILENT, true);
         updateButtons(ACTION_TOGGLE_SILENT);
     };
 
     // used by standard ui
     private OnClickListener mOnDismissSettings = v -> {
+        mPressedApply = true;
         closeControls(v);
     };
 
@@ -294,7 +302,8 @@
 
         mShowInStatusBar = !mINotificationManager.shouldHideSilentStatusIcons(
                 mContext.getPackageName());
-        // TODO: b/128445911 use show on lockscreen setting
+        mShowOnLockscreen = Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 0) == 1;
 
         bindHeader();
         bindChannelDetails();
@@ -334,6 +343,7 @@
             findViewById(R.id.non_configurable_multichannel_text).setVisibility(GONE);
             findViewById(R.id.interruptiveness_settings).setVisibility(GONE);
             ((TextView) findViewById(R.id.done)).setText(R.string.inline_done_button);
+            findViewById(R.id.turn_off_notifications).setVisibility(GONE);
         } else if (mNumUniqueChannelsInRow > 1) {
             findViewById(R.id.non_configurable_text).setVisibility(GONE);
             findViewById(R.id.interruptiveness_settings).setVisibility(GONE);
@@ -360,10 +370,10 @@
 
         if (mWasShownHighPriority) {
             updateButtons(ACTION_ALERT);
-            setImportanceSummary(ACTION_ALERT);
+            setImportanceSummary(ACTION_ALERT, false);
         } else {
             updateButtons(ACTION_TOGGLE_SILENT);
-            setImportanceSummary(ACTION_TOGGLE_SILENT);
+            setImportanceSummary(ACTION_TOGGLE_SILENT, false);
         }
     }
 
@@ -484,14 +494,6 @@
         }
     }
 
-    private boolean hasImportanceChanged() {
-        return mSingleNotificationChannel != null
-                && mChosenImportance != null
-                && (mStartingChannelImportance == IMPORTANCE_UNSPECIFIED
-                        || (mWasShownHighPriority && mChosenImportance < IMPORTANCE_DEFAULT)
-                        || (!mWasShownHighPriority && mChosenImportance >= IMPORTANCE_DEFAULT));
-    }
-
     private void saveImportance() {
         if (!mIsNonblockable
                 || mExitReason != NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS) {
@@ -526,8 +528,8 @@
     }
 
     private void updateButtons(int blockState) {
-        TextView silence = findViewById(R.id.silence);
-        TextView alert = findViewById(R.id.alert);
+        View silence = findViewById(R.id.silence);
+        View alert = findViewById(R.id.alert);
         TextView done = findViewById(R.id.done);
         switch (blockState) {
             case ACTION_TOGGLE_SILENT:
@@ -549,22 +551,28 @@
         }
     }
 
-    private void updateButtons(TextView selected, TextView unselected) {
+    private void updateButtons(View selected, View unselected) {
         selected.setBackground(mSelectedBackground);
         selected.setSelected(true);
-        selected.setTextAppearance(
-                R.style.TextAppearance_NotificationImportanceButton_Selected);
         unselected.setBackground(mUnselectedBackground);
         unselected.setSelected(false);
-        unselected.setTextAppearance(
-                R.style.TextAppearance_NotificationImportanceButton_Unselected);
     }
 
-    void setImportanceSummary(int blockState) {
-        TextView view = findViewById(R.id.description);
+    void setImportanceSummary(int blockState, boolean userTriggered) {
+        if (userTriggered) {
+            AutoTransition transition = new AutoTransition();
+            transition.setDuration(BUTTON_ANIM_TIME_MS);
+            TransitionManager.beginDelayedTransition(this, transition);
+        }
         if (blockState == ACTION_ALERT) {
+            TextView view = findViewById(R.id.alert_summary);
+            view.setVisibility(VISIBLE);
+            findViewById(R.id.silence_summary).setVisibility(GONE);
             view.setText(R.string.notification_channel_summary_default);
         } else {
+            TextView view = findViewById(R.id.silence_summary);
+            view.setVisibility(VISIBLE);
+            findViewById(R.id.alert_summary).setVisibility(GONE);
             if (mShowInStatusBar) {
                 if (mShowOnLockscreen) {
                     view.setText(R.string.notification_channel_summary_low_status_lock);
@@ -742,12 +750,12 @@
 
     @Override
     public boolean willBeRemoved() {
-        return hasImportanceChanged();
+        return false;
     }
 
     @Override
     public boolean shouldBeSaved() {
-        return hasImportanceChanged();
+        return mPressedApply;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index 586e82c..a831a5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -31,10 +31,13 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityNodeInfo;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.graphics.ColorUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.R;
+import com.android.systemui.dock.DockManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
 import com.android.systemui.statusbar.policy.AccessibilityController;
@@ -62,6 +65,7 @@
     private final UnlockMethodCache mUnlockMethodCache;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final AccessibilityController mAccessibilityController;
+    private final DockManager mDockManager;
 
     private int mLastState = 0;
     private boolean mTransientBiometricsError;
@@ -72,13 +76,26 @@
     private boolean mPulsing;
     private boolean mDozing;
     private boolean mBouncerVisible;
+    private boolean mDocked;
     private boolean mLastDozing;
     private boolean mLastPulsing;
     private boolean mLastBouncerVisible;
     private int mIconColor;
+    private float mDozeAmount;
 
     private final Runnable mDrawOffTimeout = () -> update(true /* forceUpdate */);
-    private float mDozeAmount;
+    private final DockManager.DockEventListener mDockEventListener =
+            new DockManager.DockEventListener() {
+                @Override
+                public void onEvent(int event) {
+                    boolean docked = event == DockManager.STATE_DOCKED
+                            || event == DockManager.STATE_DOCKED_HIDE;
+                    if (docked != mDocked) {
+                        mDocked = docked;
+                        update(true /* force */);
+                    }
+        }
+    };
 
     private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
             new KeyguardUpdateMonitorCallback() {
@@ -115,7 +132,8 @@
     public LockIcon(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
             StatusBarStateController statusBarStateController,
             ConfigurationController configurationController,
-            AccessibilityController accessibilityController) {
+            AccessibilityController accessibilityController,
+            @Nullable DockManager dockManager) {
         super(context, attrs);
         mContext = context;
         mUnlockMethodCache = UnlockMethodCache.getInstance(context);
@@ -123,6 +141,7 @@
         mAccessibilityController = accessibilityController;
         mConfigurationController = configurationController;
         mStatusBarStateController = statusBarStateController;
+        mDockManager = dockManager;
     }
 
     @Override
@@ -132,6 +151,9 @@
         mConfigurationController.addCallback(this);
         mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
         mUnlockMethodCache.addListener(this);
+        if (mDockManager != null) {
+            mDockManager.addListener(mDockEventListener);
+        }
         onThemeChanged();
     }
 
@@ -142,6 +164,9 @@
         mConfigurationController.removeCallback(this);
         mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
         mUnlockMethodCache.removeListener(this);
+        if (mDockManager != null) {
+            mDockManager.removeListener(mDockEventListener);
+        }
     }
 
     @Override
@@ -237,7 +262,8 @@
             mLastBouncerVisible = mBouncerVisible;
         }
 
-        setVisibility(mDozing && !mPulsing ? INVISIBLE : VISIBLE);
+        boolean invisible = mDozing && (!mPulsing || mDocked);
+        setVisibility(invisible ? INVISIBLE : VISIBLE);
         updateClickability();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java
index 79c7ab1..4603ba6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java
@@ -23,6 +23,7 @@
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.Rect;
+import android.os.SystemClock;
 import android.os.VibrationEffect;
 import android.util.MathUtils;
 import android.view.ContextThemeWrapper;
@@ -51,6 +52,11 @@
     private static final long DISAPPEAR_ARROW_ANIMATION_DURATION_MS = 100;
 
     /**
+     * The minimum time required since the first vibration effect to receive a second one
+     */
+    private static final int MIN_TIME_BETWEEN_EFFECTS_MS = 120;
+
+    /**
      * The size of the protection of the arrow in px. Only used if this is not background protected
      */
     private static final int PROTECTION_WIDTH_PX = 2;
@@ -182,6 +188,7 @@
     private int mArrowStartColor;
     private int mCurrentArrowColor;
     private float mDisappearAmount;
+    private long mVibrationTime;
 
     private DynamicAnimation.OnAnimationEndListener mSetGoneEndListener
             = new DynamicAnimation.OnAnimationEndListener() {
@@ -394,7 +401,7 @@
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL: {
                 if (mTriggerBack) {
-                    triggerBackAnimation();
+                    triggerBack();
                 } else {
                     if (mTranslationAnimation.isRunning()) {
                         mTranslationAnimation.addEndListener(mSetGoneEndListener);
@@ -521,8 +528,10 @@
         return mCurrentTranslation;
     }
 
-    private void triggerBackAnimation() {
-
+    private void triggerBack() {
+        if (SystemClock.uptimeMillis() - mVibrationTime >= MIN_TIME_BETWEEN_EFFECTS_MS) {
+            mVibratorHelper.vibrate(VibrationEffect.EFFECT_CLICK);
+        }
         mVelocityTracker.computeCurrentVelocity(1000);
         // Only do the extra translation if we're not already flinging
         boolean doExtraTranslation = Math.abs(mVelocityTracker.getXVelocity()) < 1000;
@@ -577,6 +586,7 @@
         setCurrentTranslation(0);
         mPreviousTouchTranslation = 0;
         mTotalTouchDelta = 0;
+        mVibrationTime = 0;
         setDesiredVerticalTransition(0, false /* animated */);
     }
 
@@ -599,6 +609,7 @@
         if (!mDragSlopPassed && touchTranslation > mSwipeThreshold) {
             mDragSlopPassed = true;
             mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
+            mVibrationTime = SystemClock.uptimeMillis();
 
             // Let's show the arrow and animate it in!
             mDisappearAmount = 0.0f;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index de57066..fa3377a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -24,7 +24,6 @@
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 
 import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
-import static com.android.systemui.shared.system.NavigationBarCompat.InteractionType;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
@@ -197,12 +196,6 @@
         }
 
         @Override
-        public void onInteractionFlagsChanged(@InteractionType int flags) {
-            mNavigationBarView.updateStates();
-            updateScreenPinningGestures();
-        }
-
-        @Override
         public void startAssistant(Bundle bundle) {
             mAssistManager.startAssist(bundle);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 4333200..22636de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -19,7 +19,6 @@
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 
-import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
 
@@ -637,10 +636,6 @@
         // as they are used for exiting.
         final boolean pinningActive = ActivityManagerWrapper.getInstance().isScreenPinningActive();
         if (mOverviewProxyService.isEnabled()) {
-            // Use interaction flags to show/hide navigation buttons but will be shown if required
-            // to exit screen pinning.
-            final int flags = mOverviewProxyService.getInteractionFlags();
-            disableRecent |= (flags & FLAG_SHOW_OVERVIEW_BUTTON) == 0;
             if (pinningActive) {
                 disableBack = disableHome = false;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
index a00feeb..fa2263f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
@@ -17,8 +17,10 @@
 package com.android.systemui.statusbar.phone;
 
 import static android.content.Intent.ACTION_OVERLAY_CHANGED;
-import static android.content.Intent.ACTION_USER_SWITCHED;
+import static android.os.UserHandle.USER_CURRENT;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -28,16 +30,21 @@
 import android.content.pm.PackageManager;
 import android.content.res.ApkAssets;
 import android.os.PatternMatcher;
+import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.util.Log;
+import android.util.SparseBooleanArray;
 
 import com.android.systemui.Dumpable;
+import com.android.systemui.UiOffloadThread;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -48,7 +55,7 @@
 @Singleton
 public class NavigationModeController implements Dumpable {
 
-    private static final String TAG = NavigationModeController.class.getName();
+    private static final String TAG = NavigationModeController.class.getSimpleName();
     private static final boolean DEBUG = true;
 
     public interface ModeChangedListener {
@@ -57,6 +64,10 @@
 
     private final Context mContext;
     private final IOverlayManager mOverlayManager;
+    private final DeviceProvisionedController mDeviceProvisionedController;
+    private final UiOffloadThread mUiOffloadThread;
+
+    private SparseBooleanArray mRestoreGesturalNavBarMode = new SparseBooleanArray();
 
     private int mMode = NAV_BAR_MODE_3BUTTON;
     private ArrayList<ModeChangedListener> mListeners = new ArrayList<>();
@@ -64,36 +75,90 @@
     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            context = getCurrentUserContext();
-            int mode = getCurrentInteractionMode(context);
-            mMode = mode;
-            if (DEBUG) {
-                Log.e(TAG, "ACTION_OVERLAY_CHANGED: mode=" + mMode
-                        + " contextUser=" + context.getUserId());
-                dumpAssetPaths(context);
-            }
-
-            for (int i = 0; i < mListeners.size(); i++) {
-                mListeners.get(i).onNavigationModeChanged(mode);
+            if (intent.getAction().equals(ACTION_OVERLAY_CHANGED)) {
+                if (DEBUG) {
+                    Log.d(TAG, "ACTION_OVERLAY_CHANGED");
+                }
+                updateCurrentInteractionMode(true /* notify */);
             }
         }
     };
 
+    private final DeviceProvisionedController.DeviceProvisionedListener mDeviceProvisionedCallback =
+            new DeviceProvisionedController.DeviceProvisionedListener() {
+                @Override
+                public void onDeviceProvisionedChanged() {
+                    if (DEBUG) {
+                        Log.d(TAG, "onDeviceProvisionedChanged: "
+                                + mDeviceProvisionedController.isDeviceProvisioned());
+                    }
+                    // Once the device has been provisioned, check if we can restore gestural nav
+                    restoreGesturalNavOverlayIfNecessary();
+                }
+
+                @Override
+                public void onUserSetupChanged() {
+                    if (DEBUG) {
+                        Log.d(TAG, "onUserSetupChanged: "
+                                + mDeviceProvisionedController.isCurrentUserSetup());
+                    }
+                    // Once the user has been setup, check if we can restore gestural nav
+                    restoreGesturalNavOverlayIfNecessary();
+                }
+
+                @Override
+                public void onUserSwitched() {
+                    if (DEBUG) {
+                        Log.d(TAG, "onUserSwitched: "
+                                + ActivityManagerWrapper.getInstance().getCurrentUserId());
+                    }
+
+                    // Update the nav mode for the current user
+                    updateCurrentInteractionMode(true /* notify */);
+
+                    // When switching users, defer enabling the gestural nav overlay until the user
+                    // is all set up
+                    deferGesturalNavOverlayIfNecessary();
+                }
+            };
+
     @Inject
-    public NavigationModeController(Context context) {
+    public NavigationModeController(Context context,
+            DeviceProvisionedController deviceProvisionedController,
+            UiOffloadThread uiOffloadThread) {
         mContext = context;
         mOverlayManager = IOverlayManager.Stub.asInterface(
                 ServiceManager.getService(Context.OVERLAY_SERVICE));
+        mUiOffloadThread = uiOffloadThread;
+        mDeviceProvisionedController = deviceProvisionedController;
+        mDeviceProvisionedController.addCallback(mDeviceProvisionedCallback);
 
         IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
         overlayFilter.addDataScheme("package");
         overlayFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL);
         mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, overlayFilter, null, null);
 
-        IntentFilter userFilter = new IntentFilter(ACTION_USER_SWITCHED);
-        mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, userFilter, null, null);
+        updateCurrentInteractionMode(false /* notify */);
 
-        mMode = getCurrentInteractionMode(getCurrentUserContext());
+        // Check if we need to defer enabling gestural nav
+        deferGesturalNavOverlayIfNecessary();
+    }
+
+    public void updateCurrentInteractionMode(boolean notify) {
+        Context context = getCurrentUserContext();
+        int mode = getCurrentInteractionMode(context);
+        mMode = mode;
+        if (DEBUG) {
+            Log.e(TAG, "updateCurrentInteractionMode: mode=" + mMode
+                    + " contextUser=" + context.getUserId());
+            dumpAssetPaths(context);
+        }
+
+        if (notify) {
+            for (int i = 0; i < mListeners.size(); i++) {
+                mListeners.get(i).onNavigationModeChanged(mode);
+            }
+        }
     }
 
     public int addListener(ModeChangedListener listener) {
@@ -129,10 +194,77 @@
         }
     }
 
+    private void deferGesturalNavOverlayIfNecessary() {
+        final int userId = mDeviceProvisionedController.getCurrentUser();
+        mRestoreGesturalNavBarMode.put(userId, false);
+        if (mDeviceProvisionedController.isDeviceProvisioned()
+                && mDeviceProvisionedController.isCurrentUserSetup()) {
+            // User is already setup and device is provisioned, nothing to do
+            if (DEBUG) {
+                Log.d(TAG, "deferGesturalNavOverlayIfNecessary: device is provisioned and user is "
+                        + "setup");
+            }
+            return;
+        }
+
+        ArrayList<String> defaultOverlays = new ArrayList<>();
+        try {
+            defaultOverlays.addAll(Arrays.asList(mOverlayManager.getDefaultOverlayPackages()));
+        } catch (RemoteException e) {
+            Log.e(TAG, "deferGesturalNavOverlayIfNecessary: failed to fetch default overlays");
+        }
+        if (!defaultOverlays.contains(NAV_BAR_MODE_GESTURAL_OVERLAY)) {
+            // No default gesture nav overlay
+            if (DEBUG) {
+                Log.d(TAG, "deferGesturalNavOverlayIfNecessary: no default gestural overlay, "
+                        + "default=" + defaultOverlays);
+            }
+            return;
+        }
+
+        // If the default is gestural, force-enable three button mode until the device is
+        // provisioned
+        setModeOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY, USER_CURRENT);
+        mRestoreGesturalNavBarMode.put(userId, true);
+        if (DEBUG) {
+            Log.d(TAG, "deferGesturalNavOverlayIfNecessary: setting to 3 button mode");
+        }
+    }
+
+    private void restoreGesturalNavOverlayIfNecessary() {
+        if (DEBUG) {
+            Log.d(TAG, "restoreGesturalNavOverlayIfNecessary: needs restore="
+                    + mRestoreGesturalNavBarMode);
+        }
+        final int userId = mDeviceProvisionedController.getCurrentUser();
+        if (mRestoreGesturalNavBarMode.get(userId)) {
+            // Restore the gestural state if necessary
+            setModeOverlay(NAV_BAR_MODE_GESTURAL_OVERLAY, USER_CURRENT);
+            mRestoreGesturalNavBarMode.put(userId, false);
+        }
+    }
+
+    public void setModeOverlay(String overlayPkg, int userId) {
+        mUiOffloadThread.submit(() -> {
+            try {
+                mOverlayManager.setEnabledExclusiveInCategory(overlayPkg, userId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to enable overlay " + overlayPkg + " for user " + userId);
+            }
+        });
+    }
+
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("NavigationModeController:");
         pw.println("  mode=" + mMode);
+        String defaultOverlays = "";
+        try {
+            defaultOverlays = String.join(", ", mOverlayManager.getDefaultOverlayPackages());
+        } catch (RemoteException e) {
+            defaultOverlays = "failed_to_fetch";
+        }
+        pw.println("  defaultOverlays=" + defaultOverlays);
         dumpAssetPaths(getCurrentUserContext());
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 9923ec9..41fa346 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -56,7 +56,7 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider.ButtonInterface;
 import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.shared.system.NavigationBarCompat;
+import com.android.systemui.shared.system.QuickStepContract;
 
 public class KeyButtonView extends ImageView implements ButtonInterface {
     private static final String TAG = KeyButtonView.class.getSimpleName();
@@ -244,11 +244,11 @@
                 y = (int)ev.getRawY();
 
                 boolean exceededTouchSlopX = Math.abs(x - mTouchDownX) > (mIsVertical
-                        ? NavigationBarCompat.getQuickScrubTouchSlopPx()
-                        : NavigationBarCompat.getQuickStepTouchSlopPx());
+                        ? QuickStepContract.getQuickScrubTouchSlopPx()
+                        : QuickStepContract.getQuickStepTouchSlopPx());
                 boolean exceededTouchSlopY = Math.abs(y - mTouchDownY) > (mIsVertical
-                        ? NavigationBarCompat.getQuickStepTouchSlopPx()
-                        : NavigationBarCompat.getQuickScrubTouchSlopPx());
+                        ? QuickStepContract.getQuickStepTouchSlopPx()
+                        : QuickStepContract.getQuickScrubTouchSlopPx());
                 if (exceededTouchSlopX || exceededTouchSlopY) {
                     // When quick step is enabled, prevent animating the ripple triggered by
                     // setPressed and decide to run it on touch up
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 11e5625..c398ad8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -94,8 +94,7 @@
         mPhone = phone;
         mDefaults = defaults;
         mSubscriptionInfo = info;
-        mPhoneStateListener = new MobilePhoneStateListener(info.getSubscriptionId(),
-                receiverLooper);
+        mPhoneStateListener = new MobilePhoneStateListener(receiverLooper);
         mNetworkNameSeparator = getStringIfExists(R.string.status_bar_network_name_separator);
         mNetworkNameDefault = getStringIfExists(
                 com.android.internal.R.string.lockscreen_carrier_default);
@@ -574,8 +573,8 @@
     }
 
     class MobilePhoneStateListener extends PhoneStateListener {
-        public MobilePhoneStateListener(int subId, Looper looper) {
-            super(subId, looper);
+        public MobilePhoneStateListener(Looper looper) {
+            super(looper);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index faf63c8..950f07d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -662,8 +662,9 @@
                 cachedControllers.remove(subId);
             } else {
                 MobileSignalController controller = new MobileSignalController(mContext, mConfig,
-                        mHasMobileDataFeature, mPhone, mCallbackHandler,
-                        this, subscriptions.get(i), mSubDefaults, mReceiverHandler.getLooper());
+                        mHasMobileDataFeature, mPhone.createForSubscriptionId(subId),
+                        mCallbackHandler, this, subscriptions.get(i),
+                        mSubDefaults, mReceiverHandler.getLooper());
                 controller.setUserSetupComplete(mUserSetup);
                 mMobileSignalControllers.put(subId, controller);
                 if (subscriptions.get(i).getSimSlotIndex() == 0) {
@@ -1049,7 +1050,8 @@
         SubscriptionInfo info = new SubscriptionInfo(id, "", simSlotIndex, "", "", 0, 0, "", 0,
                 null, null, null, "", false, null, null);
         MobileSignalController controller = new MobileSignalController(mContext,
-                mConfig, mHasMobileDataFeature, mPhone, mCallbackHandler, this, info,
+                mConfig, mHasMobileDataFeature,
+                mPhone.createForSubscriptionId(info.getSubscriptionId()), mCallbackHandler, this, info,
                 mSubDefaults, mReceiverHandler.getLooper());
         mMobileSignalControllers.put(id, controller);
         controller.getState().userSetup = true;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
new file mode 100644
index 0000000..d6dac2f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -0,0 +1,707 @@
+/*
+ * 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.bubbles;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bubbles.BubbleData.TimeSource;
+import com.android.systemui.statusbar.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class BubbleDataTest extends SysuiTestCase {
+
+    private NotificationEntry mEntryA1;
+    private NotificationEntry mEntryA2;
+    private NotificationEntry mEntryA3;
+    private NotificationEntry mEntryB1;
+    private NotificationEntry mEntryB2;
+    private NotificationEntry mEntryB3;
+    private NotificationEntry mEntryC1;
+
+    private Bubble mBubbleA1;
+    private Bubble mBubbleA2;
+    private Bubble mBubbleA3;
+    private Bubble mBubbleB1;
+    private Bubble mBubbleB2;
+    private Bubble mBubbleB3;
+    private Bubble mBubbleC1;
+
+    private BubbleData mBubbleData;
+
+    @Mock
+    private TimeSource mTimeSource;
+    @Mock
+    private BubbleData.Listener mListener;
+    @Mock
+    private PendingIntent mExpandIntent;
+    @Mock
+    private PendingIntent mDeleteIntent;
+
+    private NotificationTestHelper mNotificationTestHelper;
+
+    @Before
+    public void setUp() throws Exception {
+        mNotificationTestHelper = new NotificationTestHelper(mContext);
+        MockitoAnnotations.initMocks(this);
+
+        mEntryA1 = createBubbleEntry(1, "a1", "package.a");
+        mEntryA2 = createBubbleEntry(1, "a2", "package.a");
+        mEntryA3 = createBubbleEntry(1, "a3", "package.a");
+        mEntryB1 = createBubbleEntry(1, "b1", "package.b");
+        mEntryB2 = createBubbleEntry(1, "b2", "package.b");
+        mEntryB3 = createBubbleEntry(1, "b3", "package.b");
+        mEntryC1 = createBubbleEntry(1, "c1", "package.c");
+
+        mBubbleA1 = new Bubble(mEntryA1);
+        mBubbleA2 = new Bubble(mEntryA2);
+        mBubbleA3 = new Bubble(mEntryA3);
+        mBubbleB1 = new Bubble(mEntryB1);
+        mBubbleB2 = new Bubble(mEntryB2);
+        mBubbleB3 = new Bubble(mEntryB3);
+        mBubbleC1 = new Bubble(mEntryC1);
+
+        mBubbleData = new BubbleData(getContext());
+
+        // Used by BubbleData to set lastAccessedTime
+        when(mTimeSource.currentTimeMillis()).thenReturn(1000L);
+        mBubbleData.setTimeSource(mTimeSource);
+    }
+
+    private NotificationEntry createBubbleEntry(int userId, String notifKey, String packageName) {
+        return createBubbleEntry(userId, notifKey, packageName, 1000);
+    }
+
+    private void setPostTime(NotificationEntry entry, long postTime) {
+        when(entry.notification.getPostTime()).thenReturn(postTime);
+    }
+
+    private void setOngoing(NotificationEntry entry, boolean ongoing) {
+        if (ongoing) {
+            entry.notification.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
+        } else {
+            entry.notification.getNotification().flags &= ~Notification.FLAG_FOREGROUND_SERVICE;
+        }
+    }
+
+    /**
+     * No ExpandableNotificationRow is required to test BubbleData. This setup is all that is
+     * required for BubbleData functionality and verification. NotificationTestHelper is used only
+     * as a convenience to create a Notification w/BubbleMetadata.
+     */
+    private NotificationEntry createBubbleEntry(int userId, String notifKey, String packageName,
+            long postTime) {
+        // BubbleMetadata
+        Notification.BubbleMetadata bubbleMetadata = new Notification.BubbleMetadata.Builder()
+                .setIntent(mExpandIntent)
+                .setDeleteIntent(mDeleteIntent)
+                .setIcon(Icon.createWithResource("", 0))
+                .build();
+        // Notification -> BubbleMetadata
+        Notification notification = mNotificationTestHelper.createNotification(false,
+                null /* groupKey */, bubbleMetadata);
+
+        // StatusBarNotification
+        StatusBarNotification sbn = mock(StatusBarNotification.class);
+        when(sbn.getKey()).thenReturn(notifKey);
+        when(sbn.getUser()).thenReturn(new UserHandle(userId));
+        when(sbn.getPackageName()).thenReturn(packageName);
+        when(sbn.getPostTime()).thenReturn(postTime);
+        when(sbn.getNotification()).thenReturn(notification);
+
+        // NotificationEntry -> StatusBarNotification -> Notification -> BubbleMetadata
+        return new NotificationEntry(sbn);
+    }
+
+    private void sendUpdatedEntryAtTime(NotificationEntry entry, long postTime) {
+        setPostTime(entry, postTime);
+        mBubbleData.notificationEntryUpdated(entry);
+    }
+
+    private void changeExpandedStateAtTime(boolean shouldBeExpanded, long time) {
+        when(mTimeSource.currentTimeMillis()).thenReturn(time);
+        mBubbleData.setExpanded(shouldBeExpanded);
+    }
+
+    @Test
+    public void testAddBubble() {
+        // Setup
+        mBubbleData.setListener(mListener);
+
+        // Test
+        setPostTime(mEntryA1, 1000);
+        mBubbleData.notificationEntryUpdated(mEntryA1);
+
+        // Verify
+        verify(mListener).onBubbleAdded(eq(mBubbleA1));
+        verify(mListener).onSelectionChanged(eq(mBubbleA1));
+        verify(mListener).apply();
+    }
+
+    @Test
+    public void testRemoveBubble() {
+        // Setup
+        mBubbleData.notificationEntryUpdated(mEntryA1);
+        mBubbleData.notificationEntryUpdated(mEntryA2);
+        mBubbleData.notificationEntryUpdated(mEntryA3);
+        mBubbleData.setListener(mListener);
+
+        // Test
+        mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
+
+        // Verify
+        verify(mListener).onBubbleRemoved(eq(mBubbleA1), eq(BubbleController.DISMISS_USER_GESTURE));
+        verify(mListener).onSelectionChanged(eq(mBubbleA2));
+        verify(mListener).apply();
+    }
+
+    @Test
+    public void test_collapsed_addBubble_atMaxBubbles_expiresLeastActive() {
+        // Given
+        sendUpdatedEntryAtTime(mEntryA1, 1000);
+        sendUpdatedEntryAtTime(mEntryA2, 2000);
+        sendUpdatedEntryAtTime(mEntryA3, 3000);
+        sendUpdatedEntryAtTime(mEntryB1, 4000);
+        sendUpdatedEntryAtTime(mEntryB2, 5000);
+        assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1);
+
+        // When
+        sendUpdatedEntryAtTime(mEntryC1, 6000);
+
+        // Then
+        // A2 is removed. A1 is oldest but is the selected bubble.
+        assertThat(mBubbleData.getBubbles()).doesNotContain(mBubbleA2);
+    }
+
+    @Test
+    public void test_collapsed_expand_whenEmpty_doesNothing() {
+        assertThat(mBubbleData.hasBubbles()).isFalse();
+        changeExpandedStateAtTime(true, 2000L);
+
+        verify(mListener, never()).onExpandedChanged(anyBoolean());
+        verify(mListener, never()).apply();
+    }
+
+    // New bubble while stack is collapsed
+    @Test
+    public void test_collapsed_addBubble() {
+        // Given
+        assertThat(mBubbleData.hasBubbles()).isFalse();
+        assertThat(mBubbleData.isExpanded()).isFalse();
+
+        // When
+        sendUpdatedEntryAtTime(mEntryA1, 1000);
+        sendUpdatedEntryAtTime(mEntryB1, 2000);
+        sendUpdatedEntryAtTime(mEntryB2, 3000);
+        sendUpdatedEntryAtTime(mEntryA2, 4000);
+
+        // Then
+        // New bubbles move to front when collapsed, bringing bubbles from the same app along
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1));
+    }
+
+    // New bubble while collapsed with ongoing bubble present
+    @Test
+    public void test_collapsed_addBubble_withOngoing() {
+        // Given
+        assertThat(mBubbleData.hasBubbles()).isFalse();
+        assertThat(mBubbleData.isExpanded()).isFalse();
+
+        // When
+        setOngoing(mEntryA1, true);
+        setPostTime(mEntryA1, 1000);
+        mBubbleData.notificationEntryUpdated(mEntryA1);
+        setPostTime(mEntryB1, 2000);
+        mBubbleData.notificationEntryUpdated(mEntryB1);
+        setPostTime(mEntryB2, 3000);
+        mBubbleData.notificationEntryUpdated(mEntryB2);
+        setPostTime(mEntryA2, 4000);
+        mBubbleData.notificationEntryUpdated(mEntryA2);
+
+        // Then
+        // New bubbles move to front, but stay behind any ongoing bubbles.
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleA1, mBubbleA2, mBubbleB2, mBubbleB1));
+    }
+
+    // Remove the selected bubble (middle bubble), while the stack is collapsed.
+    @Test
+    public void test_collapsed_removeBubble_selected() {
+        // Given
+        assertThat(mBubbleData.hasBubbles()).isFalse();
+        assertThat(mBubbleData.isExpanded()).isFalse();
+
+        setPostTime(mEntryA1, 1000);
+        mBubbleData.notificationEntryUpdated(mEntryA1);
+
+        setPostTime(mEntryB1, 2000);
+        mBubbleData.notificationEntryUpdated(mEntryB1);
+
+        setPostTime(mEntryB2, 3000);
+        mBubbleData.notificationEntryUpdated(mEntryB2);
+
+        setPostTime(mEntryA2, 4000);
+        mBubbleData.notificationEntryUpdated(mEntryA2);
+
+        mBubbleData.setSelectedBubble(mBubbleB2);
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1));
+
+        // When
+        mBubbleData.notificationEntryRemoved(mEntryB2, BubbleController.DISMISS_USER_GESTURE);
+
+        // Then
+        // (Selection remains in the same position)
+        assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleB1);
+    }
+
+    // Remove the selected bubble (last bubble), while the stack is collapsed.
+    @Test
+    public void test_collapsed_removeSelectedBubble_inLastPosition() {
+        // Given
+        assertThat(mBubbleData.hasBubbles()).isFalse();
+        assertThat(mBubbleData.isExpanded()).isFalse();
+
+        sendUpdatedEntryAtTime(mEntryA1, 1000);
+        sendUpdatedEntryAtTime(mEntryB1, 2000);
+        sendUpdatedEntryAtTime(mEntryB2, 3000);
+        sendUpdatedEntryAtTime(mEntryA2, 4000);
+
+        mBubbleData.setSelectedBubble(mBubbleB1);
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1));
+
+        // When
+        mBubbleData.notificationEntryRemoved(mEntryB1, BubbleController.DISMISS_USER_GESTURE);
+
+        // Then
+        // (Selection is forced to move to previous)
+        assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleB2);
+    }
+
+    @Test
+    public void test_collapsed_addBubble_ongoing() {
+        // Given
+        assertThat(mBubbleData.hasBubbles()).isFalse();
+        assertThat(mBubbleData.isExpanded()).isFalse();
+
+        // When
+        setPostTime(mEntryA1, 1000);
+        mBubbleData.notificationEntryUpdated(mEntryA1);
+
+        setPostTime(mEntryB1, 2000);
+        mBubbleData.notificationEntryUpdated(mEntryB1);
+
+        setPostTime(mEntryB2, 3000);
+        setOngoing(mEntryB2, true);
+        mBubbleData.notificationEntryUpdated(mEntryB2);
+
+        setPostTime(mEntryA2, 4000);
+        mBubbleData.notificationEntryUpdated(mEntryA2);
+
+        // Then
+        // New bubbles move to front, but stay behind any ongoing bubbles.
+        // Does not break grouping. (A2 is inserted after B1, even though it's newer).
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleB2, mBubbleB1, mBubbleA2, mBubbleA1));
+    }
+
+    @Test
+    public void test_collapsed_removeBubble() {
+        // Given
+        assertThat(mBubbleData.hasBubbles()).isFalse();
+        assertThat(mBubbleData.isExpanded()).isFalse();
+
+        sendUpdatedEntryAtTime(mEntryA1, 1000);
+        sendUpdatedEntryAtTime(mEntryB1, 2000);
+        sendUpdatedEntryAtTime(mEntryB2, 3000);
+        sendUpdatedEntryAtTime(mEntryA2, 4000);
+
+        // When
+        mBubbleData.notificationEntryRemoved(mEntryB2, BubbleController.DISMISS_USER_GESTURE);
+
+        // Then
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleA2, mBubbleA1, mBubbleB1));
+    }
+
+    @Test
+    public void test_collapsed_updateBubble() {
+        // Given
+        assertThat(mBubbleData.hasBubbles()).isFalse();
+        assertThat(mBubbleData.isExpanded()).isFalse();
+
+        sendUpdatedEntryAtTime(mEntryA1, 1000);
+        sendUpdatedEntryAtTime(mEntryB1, 2000);
+        sendUpdatedEntryAtTime(mEntryB2, 3000);
+        sendUpdatedEntryAtTime(mEntryA2, 4000);
+
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1));
+
+        // When
+        sendUpdatedEntryAtTime(mEntryB2, 5000);
+
+        // Then
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleB2, mBubbleB1, mBubbleA2, mBubbleA1));
+    }
+
+    @Test
+    public void test_collapsed_updateBubble_withOngoing() {
+        // Given
+        assertThat(mBubbleData.hasBubbles()).isFalse();
+        assertThat(mBubbleData.isExpanded()).isFalse();
+
+        setPostTime(mEntryA1, 1000);
+        mBubbleData.notificationEntryUpdated(mEntryA1);
+
+        setPostTime(mEntryB1, 2000);
+        mBubbleData.notificationEntryUpdated(mEntryB1);
+
+        setPostTime(mEntryB2, 3000);
+        mBubbleData.notificationEntryUpdated(mEntryB2);
+
+        setOngoing(mEntryA2, true);
+        setPostTime(mEntryA2, 4000);
+        mBubbleData.notificationEntryUpdated(mEntryA2);
+
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1));
+
+        // When
+        setPostTime(mEntryB1, 5000);
+        mBubbleData.notificationEntryUpdated(mEntryB1);
+
+        // Then
+        // A2 remains in first position, due to being ongoing. B1 moves before B2, Group A
+        // remains before group B.
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleA2, mBubbleA1, mBubbleB1, mBubbleB2));
+    }
+
+    @Test
+    public void test_collapse_afterUpdateWhileExpanded() {
+        // Given
+        assertThat(mBubbleData.hasBubbles()).isFalse();
+        assertThat(mBubbleData.isExpanded()).isFalse();
+
+        sendUpdatedEntryAtTime(mEntryA1, 1000);
+        sendUpdatedEntryAtTime(mEntryB1, 2000);
+        sendUpdatedEntryAtTime(mEntryB2, 3000);
+        sendUpdatedEntryAtTime(mEntryA2, 4000);
+
+        assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1);
+
+        changeExpandedStateAtTime(true, 5000L);
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1));
+
+        sendUpdatedEntryAtTime(mEntryB1, 6000);
+
+        // (No reordering while expanded)
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1));
+
+        // When
+        changeExpandedStateAtTime(false, 7000L);
+
+        // Then
+        // A1 moves to front on collapse, since it is the selected bubble (and most recently
+        // accessed).
+        // A2 moves next to A1 to maintain grouping.
+        // B1 moves in front of B2, since it received an update while expanded
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleA1, mBubbleA2, mBubbleB1, mBubbleB2));
+    }
+
+    @Test
+    public void test_collapse_afterUpdateWhileExpanded_withOngoing() {
+        // Given
+        assertThat(mBubbleData.hasBubbles()).isFalse();
+        assertThat(mBubbleData.isExpanded()).isFalse();
+
+        sendUpdatedEntryAtTime(mEntryA1, 1000);
+        sendUpdatedEntryAtTime(mEntryB1, 2000);
+
+        setOngoing(mEntryB2, true);
+        sendUpdatedEntryAtTime(mEntryB2, 3000);
+
+        sendUpdatedEntryAtTime(mEntryA2, 4000);
+
+        assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1);
+
+        changeExpandedStateAtTime(true, 5000L);
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleB2, mBubbleB1, mBubbleA2, mBubbleA1));
+
+        sendUpdatedEntryAtTime(mEntryA1, 6000);
+
+        // No reordering if expanded
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleB2, mBubbleB1, mBubbleA2, mBubbleA1));
+
+        // When
+        changeExpandedStateAtTime(false, 7000L);
+
+        // Then
+        // B2 remains in first position because it is ongoing.
+        // B1 remains grouped with B2
+        // A1 moves in front of A2, since it is more recently updated (and is selected).
+        // B1 moves in front of B2, since it has more recent activity.
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleB2, mBubbleB1, mBubbleA1, mBubbleA2));
+    }
+
+    @Test
+    public void test_collapsed_removeLastBubble_clearsSelectedBubble() {
+        // Given
+        assertThat(mBubbleData.hasBubbles()).isFalse();
+        assertThat(mBubbleData.isExpanded()).isFalse();
+
+        sendUpdatedEntryAtTime(mEntryA1, 1000);
+        sendUpdatedEntryAtTime(mEntryB1, 2000);
+        sendUpdatedEntryAtTime(mEntryB2, 3000);
+        sendUpdatedEntryAtTime(mEntryA2, 4000);
+
+        assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1);
+
+        mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
+        mBubbleData.notificationEntryRemoved(mEntryB1, BubbleController.DISMISS_USER_GESTURE);
+        mBubbleData.notificationEntryRemoved(mEntryB2, BubbleController.DISMISS_USER_GESTURE);
+        mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE);
+
+        assertThat(mBubbleData.getSelectedBubble()).isNull();
+    }
+
+    @Test
+    public void test_expanded_addBubble_atMaxBubbles_expiresLeastActive() {
+        // Given
+        sendUpdatedEntryAtTime(mEntryA1, 1000);
+        assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1);
+
+        changeExpandedStateAtTime(true, 2000L);
+        assertThat(mBubbleData.getSelectedBubble().getLastActivity()).isEqualTo(2000);
+
+        sendUpdatedEntryAtTime(mEntryA2, 3000);
+        sendUpdatedEntryAtTime(mEntryA3, 4000);
+        sendUpdatedEntryAtTime(mEntryB1, 5000);
+        sendUpdatedEntryAtTime(mEntryB2, 6000);
+        sendUpdatedEntryAtTime(mEntryB3, 7000);
+
+
+        // Then
+        // A1 would be removed, but it is selected and expanded, so it should not go away.
+        // Instead, fall through to removing A2 (the next oldest).
+        assertThat(mBubbleData.getBubbles()).doesNotContain(mEntryA2);
+    }
+
+    @Test
+    public void test_expanded_removeLastBubble_collapsesStack() {
+        // Given
+        setPostTime(mEntryA1, 1000);
+        mBubbleData.notificationEntryUpdated(mEntryA1);
+
+        setPostTime(mEntryB1, 2000);
+        mBubbleData.notificationEntryUpdated(mEntryB1);
+
+        setPostTime(mEntryB2, 3000);
+        mBubbleData.notificationEntryUpdated(mEntryC1);
+
+        mBubbleData.setExpanded(true);
+
+        mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
+        mBubbleData.notificationEntryRemoved(mEntryB1, BubbleController.DISMISS_USER_GESTURE);
+        mBubbleData.notificationEntryRemoved(mEntryC1, BubbleController.DISMISS_USER_GESTURE);
+
+        assertThat(mBubbleData.isExpanded()).isFalse();
+        assertThat(mBubbleData.getSelectedBubble()).isNull();
+    }
+
+    // Bubbles do not reorder while expanded
+    @Test
+    public void test_expanded_selection_collapseToTop() {
+        // Given
+        assertThat(mBubbleData.hasBubbles()).isFalse();
+        assertThat(mBubbleData.isExpanded()).isFalse();
+
+        sendUpdatedEntryAtTime(mEntryA1, 1000);
+        sendUpdatedEntryAtTime(mEntryA2, 2000);
+        sendUpdatedEntryAtTime(mEntryB1, 3000);
+
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleB1, mBubbleA2, mBubbleA1));
+
+        changeExpandedStateAtTime(true, 4000L);
+
+        // regrouping only happens when collapsed (after new or update) or expanded->collapsed
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleB1, mBubbleA2, mBubbleA1));
+
+        changeExpandedStateAtTime(false, 6000L);
+
+        // A1 is still selected and it's lastAccessed time has been updated
+        // on collapse, sorting is applied, keeping the selected bubble at the front
+        assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1);
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleA1, mBubbleA2, mBubbleB1));
+    }
+
+    // New bubble from new app while stack is expanded
+    @Test
+    public void test_expanded_addBubble_newApp() {
+        // Given
+        sendUpdatedEntryAtTime(mEntryA1, 1000);
+        sendUpdatedEntryAtTime(mEntryA2, 2000);
+        sendUpdatedEntryAtTime(mEntryA3, 3000);
+        sendUpdatedEntryAtTime(mEntryB1, 4000);
+        sendUpdatedEntryAtTime(mEntryB2, 5000);
+
+        assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1);
+
+        changeExpandedStateAtTime(true, 6000L);
+
+        assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1);
+        assertThat(mBubbleData.getSelectedBubble().getLastActivity()).isEqualTo(6000L);
+
+        // regrouping only happens when collapsed (after new or update) or expanded->collapsed
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleB2, mBubbleB1, mBubbleA3, mBubbleA2, mBubbleA1));
+
+        // When
+        sendUpdatedEntryAtTime(mEntryC1, 7000);
+
+        // Then
+        // A2 is expired. A1 was oldest, but lastActivityTime is reset when expanded, since A1 is
+        // selected.
+        // C1 is added at the end since bubbles are expanded.
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleB2, mBubbleB1, mBubbleA3, mBubbleA1, mBubbleC1));
+    }
+
+    // New bubble from existing app while stack is expanded
+    @Test
+    public void test_expanded_addBubble_existingApp() {
+        // Given
+        sendUpdatedEntryAtTime(mEntryB1, 1000);
+        sendUpdatedEntryAtTime(mEntryB2, 2000);
+        sendUpdatedEntryAtTime(mEntryA1, 3000);
+        sendUpdatedEntryAtTime(mEntryA2, 4000);
+        sendUpdatedEntryAtTime(mEntryA3, 5000);
+
+        assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleB1);
+
+        changeExpandedStateAtTime(true, 6000L);
+
+        // B1 is first (newest, since it's just been expanded and is selected)
+        assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleB1);
+        assertThat(mBubbleData.getSelectedBubble().getLastActivity()).isEqualTo(6000L);
+
+        // regrouping only happens when collapsed (after new or update) or while collapsing
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleA3, mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1));
+
+        // When
+        sendUpdatedEntryAtTime(mEntryB3, 7000);
+
+        // Then
+        // (B2 is expired, B1 was oldest, but it's lastActivityTime is updated at the point when
+        // the stack was expanded, since it is the selected bubble.
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleA3, mBubbleA2, mBubbleA1, mBubbleB3, mBubbleB1));
+    }
+
+    // Updated bubble from existing app while stack is expanded
+    @Test
+    public void test_expanded_updateBubble_existingApp() {
+        sendUpdatedEntryAtTime(mEntryA1, 1000);
+        sendUpdatedEntryAtTime(mEntryA2, 2000);
+        sendUpdatedEntryAtTime(mEntryB1, 3000);
+        sendUpdatedEntryAtTime(mEntryB2, 4000);
+
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleB2, mBubbleB1, mBubbleA2, mBubbleA1));
+        mBubbleData.setExpanded(true);
+
+        sendUpdatedEntryAtTime(mEntryA1, 5000);
+
+        // Does not reorder while expanded (for an update).
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleB2, mBubbleB1, mBubbleA2, mBubbleA1));
+    }
+
+    @Test
+    public void test_expanded_updateBubble() {
+        // Given
+        assertThat(mBubbleData.hasBubbles()).isFalse();
+        assertThat(mBubbleData.isExpanded()).isFalse();
+
+        setPostTime(mEntryA1, 1000);
+        mBubbleData.notificationEntryUpdated(mEntryA1);
+
+        setPostTime(mEntryB1, 2000);
+        mBubbleData.notificationEntryUpdated(mEntryB1);
+
+        setPostTime(mEntryB2, 3000);
+        mBubbleData.notificationEntryUpdated(mEntryB2);
+
+        setPostTime(mEntryA2, 4000);
+        mBubbleData.notificationEntryUpdated(mEntryA2);
+
+        mBubbleData.setExpanded(true);
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1));
+
+        // When
+        setPostTime(mEntryB1, 5000);
+        mBubbleData.notificationEntryUpdated(mEntryB1);
+
+        // Then
+        // B1 remains in the same place due to being expanded
+        assertThat(mBubbleData.getBubbles()).isEqualTo(
+                ImmutableList.of(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index e4b90c5..028fd7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -234,7 +234,7 @@
      * @param bubbleMetadata the bubble metadata to use for this notification if it exists.
      * @return a notification that is in the group specified or standalone if unspecified
      */
-    private Notification createNotification(boolean isGroupSummary,
+    public Notification createNotification(boolean isGroupSummary,
             @Nullable String groupKey, @Nullable BubbleMetadata bubbleMetadata) {
         Notification publicVersion = new Notification.Builder(mContext).setSmallIcon(
                 R.drawable.ic_person)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index 25995eb..7bd25808 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -891,7 +891,31 @@
     }
 
     @Test
-    public void testWillBeRemovedReturnsFalseBeforeBind() throws Exception {
+    public void testCloseControls_withoutHittingApply() throws Exception {
+        mNotificationChannel.setImportance(IMPORTANCE_LOW);
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn,
+                (Runnable saveImportance, StatusBarNotification sbn) -> {
+                    saveImportance.run();
+                }, null, null, true, false, IMPORTANCE_LOW, false
+        );
+
+        mNotificationInfo.findViewById(R.id.alert).performClick();
+
+        assertFalse(mNotificationInfo.shouldBeSaved());
+    }
+
+    @Test
+    public void testWillBeRemovedReturnsFalse() throws Exception {
+        assertFalse(mNotificationInfo.willBeRemoved());
+
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn,
+                (Runnable saveImportance, StatusBarNotification sbn) -> {
+                    saveImportance.run();
+                }, null, null, true, false, IMPORTANCE_LOW, false
+        );
+
         assertFalse(mNotificationInfo.willBeRemoved());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index ce5bfce..616b46a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -130,6 +130,7 @@
         when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(true);
         when(mMockCm.getDefaultNetworkCapabilitiesForUser(0)).thenReturn(
                 new NetworkCapabilities[] { mNetCapabilities });
+        when(mMockTm.createForSubscriptionId(anyInt())).thenReturn(mMockTm);
 
         mSignalStrength = mock(SignalStrength.class);
         mServiceState = mock(ServiceState.class);
diff --git a/packages/overlays/AccentColorBlackOverlay/Android.mk b/packages/overlays/AccentColorBlackOverlay/Android.mk
index b81ae5bb..a689def 100644
--- a/packages/overlays/AccentColorBlackOverlay/Android.mk
+++ b/packages/overlays/AccentColorBlackOverlay/Android.mk
@@ -18,7 +18,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := AccentColorBlack
-LOCAL_CERTIFICATE := platform
+
 LOCAL_PRODUCT_MODULE := true
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/packages/overlays/AccentColorCinnamonOverlay/Android.mk b/packages/overlays/AccentColorCinnamonOverlay/Android.mk
index d53c114..3a6cbe3 100644
--- a/packages/overlays/AccentColorCinnamonOverlay/Android.mk
+++ b/packages/overlays/AccentColorCinnamonOverlay/Android.mk
@@ -18,7 +18,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := AccentColorCinnamon
-LOCAL_CERTIFICATE := platform
+
 LOCAL_PRODUCT_MODULE := true
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/packages/overlays/AccentColorGreenOverlay/Android.mk b/packages/overlays/AccentColorGreenOverlay/Android.mk
index db92157..d96dbe1 100644
--- a/packages/overlays/AccentColorGreenOverlay/Android.mk
+++ b/packages/overlays/AccentColorGreenOverlay/Android.mk
@@ -18,7 +18,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := AccentColorGreen
-LOCAL_CERTIFICATE := platform
+
 LOCAL_PRODUCT_MODULE := true
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/packages/overlays/AccentColorOceanOverlay/Android.mk b/packages/overlays/AccentColorOceanOverlay/Android.mk
index a28fc72..cf0c6b3 100644
--- a/packages/overlays/AccentColorOceanOverlay/Android.mk
+++ b/packages/overlays/AccentColorOceanOverlay/Android.mk
@@ -18,7 +18,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := AccentColorOcean
-LOCAL_CERTIFICATE := platform
+
 LOCAL_PRODUCT_MODULE := true
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/packages/overlays/AccentColorOrchidOverlay/Android.mk b/packages/overlays/AccentColorOrchidOverlay/Android.mk
index c635890..fc55bef 100644
--- a/packages/overlays/AccentColorOrchidOverlay/Android.mk
+++ b/packages/overlays/AccentColorOrchidOverlay/Android.mk
@@ -18,7 +18,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := AccentColorOrchid
-LOCAL_CERTIFICATE := platform
+
 LOCAL_PRODUCT_MODULE := true
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/packages/overlays/AccentColorPurpleOverlay/Android.mk b/packages/overlays/AccentColorPurpleOverlay/Android.mk
index d7dc497..3a28efa 100644
--- a/packages/overlays/AccentColorPurpleOverlay/Android.mk
+++ b/packages/overlays/AccentColorPurpleOverlay/Android.mk
@@ -18,7 +18,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := AccentColorPurple
-LOCAL_CERTIFICATE := platform
+
 LOCAL_PRODUCT_MODULE := true
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/packages/overlays/AccentColorSpaceOverlay/Android.mk b/packages/overlays/AccentColorSpaceOverlay/Android.mk
index a0edb96..78cbf73 100644
--- a/packages/overlays/AccentColorSpaceOverlay/Android.mk
+++ b/packages/overlays/AccentColorSpaceOverlay/Android.mk
@@ -18,7 +18,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := AccentColorSpace
-LOCAL_CERTIFICATE := platform
+
 LOCAL_PRODUCT_MODULE := true
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/packages/overlays/DisplayCutoutEmulationCornerOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationCornerOverlay/Android.mk
index bf2b631..b73aea3 100644
--- a/packages/overlays/DisplayCutoutEmulationCornerOverlay/Android.mk
+++ b/packages/overlays/DisplayCutoutEmulationCornerOverlay/Android.mk
@@ -2,7 +2,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := DisplayCutoutEmulationCorner
-LOCAL_CERTIFICATE := platform
+
 
 LOCAL_PRODUCT_MODULE := true
 
diff --git a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/Android.mk
index 7042906..8ca2dad 100644
--- a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/Android.mk
+++ b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/Android.mk
@@ -2,7 +2,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := DisplayCutoutEmulationDouble
-LOCAL_CERTIFICATE := platform
+
 
 LOCAL_PRODUCT_MODULE := true
 
diff --git a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationNarrowOverlay/Android.mk
index ae69e11..7458cb5 100644
--- a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/Android.mk
+++ b/packages/overlays/DisplayCutoutEmulationNarrowOverlay/Android.mk
@@ -2,7 +2,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := DisplayCutoutEmulationNarrow
-LOCAL_CERTIFICATE := platform
+
 
 LOCAL_PRODUCT_MODULE := true
 
diff --git a/packages/overlays/DisplayCutoutEmulationTallOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationTallOverlay/Android.mk
index 7dcadfb..1a405e2 100644
--- a/packages/overlays/DisplayCutoutEmulationTallOverlay/Android.mk
+++ b/packages/overlays/DisplayCutoutEmulationTallOverlay/Android.mk
@@ -2,7 +2,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := DisplayCutoutEmulationTall
-LOCAL_CERTIFICATE := platform
+
 
 LOCAL_PRODUCT_MODULE := true
 
diff --git a/packages/overlays/DisplayCutoutEmulationWideOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationWideOverlay/Android.mk
index 3f7be73..3ebc540 100644
--- a/packages/overlays/DisplayCutoutEmulationWideOverlay/Android.mk
+++ b/packages/overlays/DisplayCutoutEmulationWideOverlay/Android.mk
@@ -2,7 +2,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := DisplayCutoutEmulationWide
-LOCAL_CERTIFICATE := platform
+
 
 LOCAL_PRODUCT_MODULE := true
 
diff --git a/packages/overlays/FontNotoSerifSourceOverlay/Android.mk b/packages/overlays/FontNotoSerifSourceOverlay/Android.mk
index 6f3c4f7..f4eedaf 100644
--- a/packages/overlays/FontNotoSerifSourceOverlay/Android.mk
+++ b/packages/overlays/FontNotoSerifSourceOverlay/Android.mk
@@ -18,7 +18,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := FontNotoSerifSource
-LOCAL_CERTIFICATE := platform
+
 LOCAL_PRODUCT_MODULE := true
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/packages/overlays/IconPackCircularAndroidOverlay/Android.mk b/packages/overlays/IconPackCircularAndroidOverlay/Android.mk
index 60f525b6..8f3baa5 100644
--- a/packages/overlays/IconPackCircularAndroidOverlay/Android.mk
+++ b/packages/overlays/IconPackCircularAndroidOverlay/Android.mk
@@ -17,7 +17,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := IconPackCircularAndroid
-LOCAL_CERTIFICATE := platform
+
 LOCAL_PRODUCT_MODULE := true
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/packages/overlays/IconPackCircularLauncherOverlay/Android.mk b/packages/overlays/IconPackCircularLauncherOverlay/Android.mk
index a5277fa..310bdef 100644
--- a/packages/overlays/IconPackCircularLauncherOverlay/Android.mk
+++ b/packages/overlays/IconPackCircularLauncherOverlay/Android.mk
@@ -18,7 +18,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := IconPackCircularLauncher
-LOCAL_CERTIFICATE := platform
+
 LOCAL_PRODUCT_MODULE := true
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/packages/overlays/IconPackCircularSettingsOverlay/Android.mk b/packages/overlays/IconPackCircularSettingsOverlay/Android.mk
index ad7324d..d067322 100644
--- a/packages/overlays/IconPackCircularSettingsOverlay/Android.mk
+++ b/packages/overlays/IconPackCircularSettingsOverlay/Android.mk
@@ -18,7 +18,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := IconPackCircularSettings
-LOCAL_CERTIFICATE := platform
+
 LOCAL_PRODUCT_MODULE := true
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/packages/overlays/IconPackCircularSystemUIOverlay/Android.mk b/packages/overlays/IconPackCircularSystemUIOverlay/Android.mk
index 711063d..5e0dcbe 100644
--- a/packages/overlays/IconPackCircularSystemUIOverlay/Android.mk
+++ b/packages/overlays/IconPackCircularSystemUIOverlay/Android.mk
@@ -18,7 +18,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := IconPackCircularSystemUI
-LOCAL_CERTIFICATE := platform
+
 LOCAL_PRODUCT_MODULE := true
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/packages/overlays/IconPackFilledAndroidOverlay/Android.mk b/packages/overlays/IconPackFilledAndroidOverlay/Android.mk
index e0db3a2..3036f7d 100644
--- a/packages/overlays/IconPackFilledAndroidOverlay/Android.mk
+++ b/packages/overlays/IconPackFilledAndroidOverlay/Android.mk
@@ -17,7 +17,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := IconPackFilledAndroid
-LOCAL_CERTIFICATE := platform
+
 LOCAL_PRODUCT_MODULE := true
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/packages/overlays/IconPackFilledLauncherOverlay/Android.mk b/packages/overlays/IconPackFilledLauncherOverlay/Android.mk
index d2e5b60..2460fa4 100644
--- a/packages/overlays/IconPackFilledLauncherOverlay/Android.mk
+++ b/packages/overlays/IconPackFilledLauncherOverlay/Android.mk
@@ -18,7 +18,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := IconPackFilledLauncher
-LOCAL_CERTIFICATE := platform
+
 LOCAL_PRODUCT_MODULE := true
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/packages/overlays/IconPackFilledSettingsOverlay/Android.mk b/packages/overlays/IconPackFilledSettingsOverlay/Android.mk
index 0443560..3cc071d 100644
--- a/packages/overlays/IconPackFilledSettingsOverlay/Android.mk
+++ b/packages/overlays/IconPackFilledSettingsOverlay/Android.mk
@@ -18,7 +18,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := IconPackFilledSettings
-LOCAL_CERTIFICATE := platform
+
 LOCAL_PRODUCT_MODULE := true
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/packages/overlays/IconPackFilledSystemUIOverlay/Android.mk b/packages/overlays/IconPackFilledSystemUIOverlay/Android.mk
index 2506132..f027692 100644
--- a/packages/overlays/IconPackFilledSystemUIOverlay/Android.mk
+++ b/packages/overlays/IconPackFilledSystemUIOverlay/Android.mk
@@ -18,7 +18,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := IconPackFilledSystemUI
-LOCAL_CERTIFICATE := platform
+
 LOCAL_PRODUCT_MODULE := true
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/packages/overlays/IconPackRoundedAndroidOverlay/Android.mk b/packages/overlays/IconPackRoundedAndroidOverlay/Android.mk
index 2937fb8..c6ad4ac 100644
--- a/packages/overlays/IconPackRoundedAndroidOverlay/Android.mk
+++ b/packages/overlays/IconPackRoundedAndroidOverlay/Android.mk
@@ -17,7 +17,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := IconPackRoundedAndroid
-LOCAL_CERTIFICATE := platform
+
 LOCAL_PRODUCT_MODULE := true
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/packages/overlays/IconPackRoundedLauncherOverlay/Android.mk b/packages/overlays/IconPackRoundedLauncherOverlay/Android.mk
index 7adfe3b..713e281 100644
--- a/packages/overlays/IconPackRoundedLauncherOverlay/Android.mk
+++ b/packages/overlays/IconPackRoundedLauncherOverlay/Android.mk
@@ -18,7 +18,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := IconPackRoundedLauncher
-LOCAL_CERTIFICATE := platform
+
 LOCAL_PRODUCT_MODULE := true
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/packages/overlays/IconPackRoundedSettingsOverlay/Android.mk b/packages/overlays/IconPackRoundedSettingsOverlay/Android.mk
index 44ac6dd..6c77519 100644
--- a/packages/overlays/IconPackRoundedSettingsOverlay/Android.mk
+++ b/packages/overlays/IconPackRoundedSettingsOverlay/Android.mk
@@ -18,7 +18,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := IconPackRoundedSettings
-LOCAL_CERTIFICATE := platform
+
 LOCAL_PRODUCT_MODULE := true
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/packages/overlays/IconPackRoundedSystemUIOverlay/Android.mk b/packages/overlays/IconPackRoundedSystemUIOverlay/Android.mk
index 2d34a54..4e21b41 100644
--- a/packages/overlays/IconPackRoundedSystemUIOverlay/Android.mk
+++ b/packages/overlays/IconPackRoundedSystemUIOverlay/Android.mk
@@ -18,7 +18,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := IconPackRoundedSystemUI
-LOCAL_CERTIFICATE := platform
+
 LOCAL_PRODUCT_MODULE := true
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/packages/overlays/IconShapeRoundedRectOverlay/Android.mk b/packages/overlays/IconShapeRoundedRectOverlay/Android.mk
index 08428d1..21cd011 100644
--- a/packages/overlays/IconShapeRoundedRectOverlay/Android.mk
+++ b/packages/overlays/IconShapeRoundedRectOverlay/Android.mk
@@ -18,7 +18,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := IconShapeRoundedRect
-LOCAL_CERTIFICATE := platform
+
 LOCAL_PRODUCT_MODULE := true
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/packages/overlays/IconShapeSquareOverlay/Android.mk b/packages/overlays/IconShapeSquareOverlay/Android.mk
index ceb745a..c872883 100644
--- a/packages/overlays/IconShapeSquareOverlay/Android.mk
+++ b/packages/overlays/IconShapeSquareOverlay/Android.mk
@@ -18,7 +18,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := IconShapeSquare
-LOCAL_CERTIFICATE := platform
+
 LOCAL_PRODUCT_MODULE := true
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/packages/overlays/IconShapeSquircleOverlay/Android.mk b/packages/overlays/IconShapeSquircleOverlay/Android.mk
index 34edc3b..fa5fe69 100644
--- a/packages/overlays/IconShapeSquircleOverlay/Android.mk
+++ b/packages/overlays/IconShapeSquircleOverlay/Android.mk
@@ -18,7 +18,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := IconShapeSquircle
-LOCAL_CERTIFICATE := platform
+
 LOCAL_PRODUCT_MODULE := true
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/packages/overlays/IconShapeTeardropOverlay/Android.mk b/packages/overlays/IconShapeTeardropOverlay/Android.mk
index 834a1c3..d5f01f3 100644
--- a/packages/overlays/IconShapeTeardropOverlay/Android.mk
+++ b/packages/overlays/IconShapeTeardropOverlay/Android.mk
@@ -18,7 +18,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := IconShapeTeardrop
-LOCAL_CERTIFICATE := platform
+
 LOCAL_PRODUCT_MODULE := true
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/packages/overlays/NavigationBarMode2ButtonOverlay/Android.mk b/packages/overlays/NavigationBarMode2ButtonOverlay/Android.mk
index 410d6d8..be86ef2 100644
--- a/packages/overlays/NavigationBarMode2ButtonOverlay/Android.mk
+++ b/packages/overlays/NavigationBarMode2ButtonOverlay/Android.mk
@@ -18,7 +18,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := NavigationBarMode2Button
-LOCAL_CERTIFICATE := platform
+
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
diff --git a/packages/overlays/NavigationBarMode3ButtonOverlay/Android.mk b/packages/overlays/NavigationBarMode3ButtonOverlay/Android.mk
index 2bc9a6a..f44a362 100644
--- a/packages/overlays/NavigationBarMode3ButtonOverlay/Android.mk
+++ b/packages/overlays/NavigationBarMode3ButtonOverlay/Android.mk
@@ -18,7 +18,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := NavigationBarMode3Button
-LOCAL_CERTIFICATE := platform
+
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/Android.mk b/packages/overlays/NavigationBarModeGesturalOverlay/Android.mk
index 5f7e0eb..02e2074 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlay/Android.mk
+++ b/packages/overlays/NavigationBarModeGesturalOverlay/Android.mk
@@ -18,7 +18,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_RRO_THEME := NavigationBarModeGestural
-LOCAL_CERTIFICATE := platform
+
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 9e73684d..9eda9db 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -33,7 +33,6 @@
 import static com.android.server.autofill.Helper.sDebug;
 import static com.android.server.autofill.Helper.sVerbose;
 import static com.android.server.autofill.Helper.toArray;
-import static com.android.server.autofill.ViewState.STATE_RESTARTED_SESSION;
 import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
 import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE;
 
@@ -564,7 +563,8 @@
      * Reads a new structure and then request a new fill response from the fill service.
      */
     @GuardedBy("mLock")
-    private void requestNewFillResponseLocked(int flags) {
+    private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState,
+            int flags) {
         if (mForAugmentedAutofillOnly || (flags & FLAG_AUGMENTED_AUTOFILL_REQUEST) != 0) {
             // TODO(b/122858578): log metrics
             if (sVerbose) {
@@ -575,6 +575,7 @@
             triggerAugmentedAutofillLocked();
             return;
         }
+        viewState.setState(newState);
 
         int requestId;
 
@@ -2165,19 +2166,17 @@
             @NonNull ViewState viewState, int flags) {
         if ((flags & FLAG_MANUAL_REQUEST) != 0) {
             if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags);
-            viewState.setState(STATE_RESTARTED_SESSION);
-            requestNewFillResponseLocked(flags);
+            requestNewFillResponseLocked(viewState, ViewState.STATE_RESTARTED_SESSION, flags);
             return;
         }
 
         // If it's not, then check if it it should start a partition.
         if (shouldStartNewPartitionLocked(id)) {
             if (sDebug) {
-                Slog.d(TAG, "Starting partition for view id " + id + ": "
+                Slog.d(TAG, "Starting partition or augmented request for view id " + id + ": "
                         + viewState.getStateAsString());
             }
-            viewState.setState(ViewState.STATE_STARTED_PARTITION);
-            requestNewFillResponseLocked(flags);
+            requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_PARTITION, flags);
         } else {
             if (sVerbose) {
                 Slog.v(TAG, "Not starting new partition for view " + id + ": "
@@ -2283,8 +2282,7 @@
                 // View is triggering autofill.
                 mCurrentViewId = viewState.id;
                 viewState.update(value, virtualBounds, flags);
-                viewState.setState(ViewState.STATE_STARTED_SESSION);
-                requestNewFillResponseLocked(flags);
+                requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_SESSION, flags);
                 break;
             case ACTION_VALUE_CHANGED:
                 if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) {
@@ -2386,6 +2384,10 @@
                     Slog.v(TAG, "entered on virtual child " + id + ": " + virtualBounds);
                 }
 
+                // Update the view states first...
+                mCurrentViewId = viewState.id;
+                viewState.setCurrentValue(value);
+
                 if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) {
                     if (sDebug) Slog.d(TAG, "Ignoring VIEW_ENTERED on URL BAR (id=" + id + ")");
                     return;
@@ -2397,10 +2399,6 @@
 
                     if (sDebug) Slog.d(TAG, "updateLocked(" + id + "): augmented-autofillable");
 
-                    // Update the view states first...
-                    mCurrentViewId = viewState.id;
-                    viewState.setCurrentValue(value);
-
                     // ...then trigger the augmented autofill UI
                     triggerAugmentedAutofillLocked();
                     return;
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index da91187..28bc348 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -66,6 +66,7 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyPermissions;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.am.BatteryStatsService;
@@ -1164,36 +1165,33 @@
 
     @Override
     public void notifyCarrierNetworkChange(boolean active) {
-        // only CarrierService with carrier privilege rule should have the permission.
-        int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-        try {
-            subId = Arrays.stream(SubscriptionManager.from(mContext)
+        // only CarrierService with carrier privilege rule should have the permission
+        int[] subIds = Arrays.stream(SubscriptionManager.from(mContext)
                     .getActiveSubscriptionIdList())
-                    .filter(i -> TelephonyPermissions.checkCarrierPrivilegeForSubId(i))
-                    .findFirst().getAsInt();
-        } catch (NoSuchElementException ex) {
+                    .filter(i -> TelephonyPermissions.checkCarrierPrivilegeForSubId(i)).toArray();
+        if (ArrayUtils.isEmpty(subIds)) {
             loge("notifyCarrierNetworkChange without carrier privilege");
-        }
-        // the active subId does not have carrier privilege. 
-        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            // the active subId does not have carrier privilege.
             throw new SecurityException("notifyCarrierNetworkChange without carrier privilege");
         }
-        int phoneId = SubscriptionManager.getPhoneId(subId);
-
-        if (VDBG) {
-            log("notifyCarrierNetworkChange: active=" + active + "subId: " + subId);
-        }
 
         synchronized (mRecords) {
             mCarrierNetworkChangeState = active;
-            for (Record r : mRecords) {
-                if (r.matchPhoneStateListenerEvent(
-                        PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE) &&
-                        idMatch(r.subId, subId, phoneId)) {
-                    try {
-                        r.callback.onCarrierNetworkChange(active);
-                    } catch (RemoteException ex) {
-                        mRemoveList.add(r.binder);
+            for (int subId : subIds) {
+                int phoneId = SubscriptionManager.getPhoneId(subId);
+
+                if (VDBG) {
+                    log("notifyCarrierNetworkChange: active=" + active + "subId: " + subId);
+                }
+                for (Record r : mRecords) {
+                    if (r.matchPhoneStateListenerEvent(
+                            PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE) &&
+                            idMatch(r.subId, subId, phoneId)) {
+                        try {
+                            r.callback.onCarrierNetworkChange(active);
+                        } catch (RemoteException ex) {
+                            mRemoveList.add(r.binder);
+                        }
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index d5883bb..3a397cd 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -18,6 +18,7 @@
 
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
@@ -1801,7 +1802,8 @@
      *
      * @see #maybeQueueReadyJobsForExecutionLocked
      */
-    private JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule) {
+    @VisibleForTesting
+    JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule) {
         final long elapsedNowMillis = sElapsedRealtimeClock.millis();
         final JobInfo job = failureToReschedule.getJob();
 
@@ -1848,6 +1850,10 @@
                 elapsedNowMillis + delayMillis,
                 JobStatus.NO_LATEST_RUNTIME, backoffAttempts,
                 failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis());
+        if (job.isPeriodic()) {
+            newJob.setOriginalLatestRunTimeElapsed(
+                    failureToReschedule.getOriginalLatestRunTimeElapsed());
+        }
         for (int ic=0; ic<mControllers.size(); ic++) {
             StateController controller = mControllers.get(ic);
             controller.rescheduleForFailureLocked(newJob, failureToReschedule);
@@ -1868,23 +1874,41 @@
      * @return A new job representing the execution criteria for this instantiation of the
      * recurring job.
      */
-    private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
+    @VisibleForTesting
+    JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
         final long elapsedNow = sElapsedRealtimeClock.millis();
-        // Compute how much of the period is remaining.
-        long runEarly = 0L;
+        final long newLatestRuntimeElapsed;
+        final long period = periodicToReschedule.getJob().getIntervalMillis();
+        final long latestRunTimeElapsed = periodicToReschedule.getOriginalLatestRunTimeElapsed();
+        final long flex = periodicToReschedule.getJob().getFlexMillis();
 
-        // If this periodic was rescheduled it won't have a deadline.
-        if (periodicToReschedule.hasDeadlineConstraint()) {
-            runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0L);
+        if (elapsedNow > latestRunTimeElapsed) {
+            // The job ran past its expected run window. Have it count towards the current window
+            // and schedule a new job for the next window.
+            if (DEBUG) {
+                Slog.i(TAG, "Periodic job ran after its intended window.");
+            }
+            final long diffMs = (elapsedNow - latestRunTimeElapsed);
+            int numSkippedWindows = (int) (diffMs / period) + 1; // +1 to include original window
+            if (period != flex && diffMs > Math.min(30 * MINUTE_IN_MILLIS, (period - flex) / 2)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Custom flex job ran too close to next window.");
+                }
+                // For custom flex periods, if the job was run too close to the next window,
+                // skip the next window and schedule for the following one.
+                numSkippedWindows += 1;
+            }
+            newLatestRuntimeElapsed = latestRunTimeElapsed + (period * numSkippedWindows);
+        } else {
+            newLatestRuntimeElapsed = latestRunTimeElapsed + period;
         }
-        long flex = periodicToReschedule.getJob().getFlexMillis();
-        long period = periodicToReschedule.getJob().getIntervalMillis();
-        long newLatestRuntimeElapsed = elapsedNow + runEarly + period;
-        long newEarliestRunTimeElapsed = newLatestRuntimeElapsed - flex;
+
+        final long newEarliestRunTimeElapsed = newLatestRuntimeElapsed - flex;
 
         if (DEBUG) {
             Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
-                    newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
+                    newEarliestRunTimeElapsed / 1000 + ", " + newLatestRuntimeElapsed / 1000
+                    + "]s");
         }
         return new JobStatus(periodicToReschedule, getCurrentHeartbeat(),
                 newEarliestRunTimeElapsed, newLatestRuntimeElapsed,
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 48f21e4..fd20e11 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -161,6 +161,12 @@
      */
     private final long latestRunTimeElapsedMillis;
 
+    /**
+     * Valid only for periodic jobs. The original latest point in the future at which this
+     * job was expected to run.
+     */
+    private long mOriginalLatestRunTimeElapsedMillis;
+
     /** How many times this job has failed, used to compute back-off. */
     private final int numFailures;
 
@@ -394,6 +400,7 @@
 
         this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis;
         this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
+        this.mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
         this.numFailures = numFailures;
 
         int requiredConstraints = job.getConstraintFlags();
@@ -871,6 +878,14 @@
         return latestRunTimeElapsedMillis;
     }
 
+    public long getOriginalLatestRunTimeElapsed() {
+        return mOriginalLatestRunTimeElapsedMillis;
+    }
+
+    public void setOriginalLatestRunTimeElapsed(long latestRunTimeElapsed) {
+        mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsed;
+    }
+
     /**
      * Return the fractional position of "now" within the "run time" window of
      * this job.
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index c6f6c50a..19ff2c1 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -1164,6 +1164,15 @@
 
             // Carrier might want to manage notifications themselves
             final PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId);
+            if (!CarrierConfigManager.isConfigForIdentifiedCarrier(config)) {
+                if (LOGV) Slog.v(TAG, "isConfigForIdentifiedCarrier returned false");
+                // Don't show notifications until we confirm that the loaded config is from an
+                // identified carrier, which may want to manage their own notifications. This method
+                // should be called every time the carrier config changes anyways, and there's no
+                // reason to alert if there isn't a carrier.
+                return;
+            }
+
             final boolean notifyWarning = getBooleanDefeatingNullable(config,
                     KEY_DATA_WARNING_NOTIFICATION_BOOL, true);
             final boolean notifyLimit = getBooleanDefeatingNullable(config,
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index ee07c7d..209ccda 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -720,6 +720,26 @@
         }
 
         @Override
+        public String[] getDefaultOverlayPackages() throws RemoteException {
+            try {
+                traceBegin(TRACE_TAG_RRO, "OMS#getDefaultOverlayPackages");
+                getContext().enforceCallingOrSelfPermission(
+                        android.Manifest.permission.MODIFY_THEME_OVERLAY, null);
+
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    synchronized (mLock) {
+                        return mImpl.getDefaultOverlayPackages();
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            } finally {
+                traceEnd(TRACE_TAG_RRO);
+            }
+        }
+
+        @Override
         public void onShellCommand(@NonNull final FileDescriptor in,
                 @NonNull final FileDescriptor out, @NonNull final FileDescriptor err,
                 @NonNull final String[] args, @NonNull final ShellCallback callback,
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 3a84b1e..092dbc8 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -641,6 +641,10 @@
         pw.println("Default overlays: " + TextUtils.join(";", mDefaultOverlays));
     }
 
+    @NonNull String[] getDefaultOverlayPackages() {
+        return mDefaultOverlays;
+    }
+
     List<String> getEnabledOverlayPackageNames(@NonNull final String targetPackageName,
             final int userId) {
         final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackageName,
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 5430f4c..497385f 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -22,22 +22,21 @@
 import android.apex.ApexInfoList;
 import android.apex.ApexSessionInfo;
 import android.apex.IApexService;
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.PackageParserException;
+import android.os.HandlerThread;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
+import android.os.SystemClock;
 import android.sysprop.ApexProperties;
 import android.util.Slog;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.SystemService;
 
 import java.io.File;
 import java.io.PrintWriter;
@@ -46,75 +45,108 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.CountDownLatch;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
 /**
  * ApexManager class handles communications with the apex service to perform operation and queries,
  * as well as providing caching to avoid unnecessary calls to the service.
+ *
+ * @hide
  */
-class ApexManager {
-    static final String TAG = "ApexManager";
-    private final IApexService mApexService;
-    private final Context mContext;
-    private final Object mLock = new Object();
-    @GuardedBy("mLock")
+public final class ApexManager extends SystemService {
+    private static final String TAG = "ApexManager";
+    private IApexService mApexService;
+
+    private final CountDownLatch mActivePackagesCacheLatch = new CountDownLatch(1);
     private Map<String, PackageInfo> mActivePackagesCache;
 
-    ApexManager(Context context) {
+    private final CountDownLatch mApexFilesCacheLatch = new CountDownLatch(1);
+    private ApexInfo[] mApexFiles;
+
+    public ApexManager(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onStart() {
         try {
             mApexService = IApexService.Stub.asInterface(
-                ServiceManager.getServiceOrThrow("apexservice"));
+                    ServiceManager.getServiceOrThrow("apexservice"));
         } catch (ServiceNotFoundException e) {
             throw new IllegalStateException("Required service apexservice not available");
         }
-        mContext = context;
+        publishLocalService(ApexManager.class, this);
+        HandlerThread oneShotThread = new HandlerThread("ApexManagerOneShotHandler");
+        oneShotThread.start();
+        oneShotThread.getThreadHandler().post(this::initSequence);
+        oneShotThread.quitSafely();
     }
 
-    void systemReady() {
-        mContext.registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                onBootCompleted();
-                mContext.unregisterReceiver(this);
-            }
-        }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
+    private void initSequence() {
+        populateApexFilesCache();
+        parseApexFiles();
     }
 
-    private void populateActivePackagesCacheIfNeeded() {
-        synchronized (mLock) {
-            if (mActivePackagesCache != null) {
-                return;
-            }
+    private void populateApexFilesCache() {
+        if (mApexFiles != null) {
+            return;
+        }
+        long startTimeMicros = SystemClock.currentTimeMicro();
+        Slog.i(TAG, "Starting to populate apex files cache");
+        try {
+            mApexFiles = mApexService.getActivePackages();
+            Slog.i(TAG, "IPC to apexd finished in " + (SystemClock.currentTimeMicro()
+                    - startTimeMicros) + " μs");
+        } catch (RemoteException re) {
+            // TODO: make sure this error is propagated to system server.
+            Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
+            re.rethrowAsRuntimeException();
+        }
+        mApexFilesCacheLatch.countDown();
+        Slog.i(TAG, "Finished populating apex files cache in " + (SystemClock.currentTimeMicro()
+                - startTimeMicros) + " μs");
+    }
+
+    private void parseApexFiles() {
+        waitForLatch(mApexFilesCacheLatch);
+        if (mApexFiles == null) {
+            throw new IllegalStateException("mApexFiles must be populated");
+        }
+        long startTimeMicros = SystemClock.currentTimeMicro();
+        Slog.i(TAG, "Starting to parse apex files");
+        List<PackageInfo> list = new ArrayList<>();
+        // TODO: this can be parallelized.
+        for (ApexInfo ai : mApexFiles) {
             try {
-                List<PackageInfo> list = new ArrayList<>();
-                final ApexInfo[] activePkgs = mApexService.getActivePackages();
-                for (ApexInfo ai : activePkgs) {
-                    // If the device is using flattened APEX, don't report any APEX
-                    // packages since they won't be managed or updated by PackageManager.
-                    if ((new File(ai.packagePath)).isDirectory()) {
-                        break;
-                    }
-                    try {
-                        list.add(PackageParser.generatePackageInfoFromApex(
-                                new File(ai.packagePath), PackageManager.GET_META_DATA
-                                | PackageManager.GET_SIGNING_CERTIFICATES));
-                    } catch (PackageParserException pe) {
-                        throw new IllegalStateException("Unable to parse: " + ai, pe);
-                    }
+                // If the device is using flattened APEX, don't report any APEX
+                // packages since they won't be managed or updated by PackageManager.
+                if ((new File(ai.packagePath)).isDirectory()) {
+                    break;
                 }
-                mActivePackagesCache = list.stream().collect(
-                        Collectors.toMap(p -> p.packageName, Function.identity()));
-            } catch (RemoteException re) {
-                Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
-                throw new RuntimeException(re);
+                list.add(PackageParser.generatePackageInfoFromApex(
+                        new File(ai.packagePath), PackageManager.GET_META_DATA
+                                | PackageManager.GET_SIGNING_CERTIFICATES));
+            } catch (PackageParserException pe) {
+                // TODO: make sure this error is propagated to system server.
+                throw new IllegalStateException("Unable to parse: " + ai, pe);
             }
         }
+        mActivePackagesCache = list.stream().collect(
+                Collectors.toMap(p -> p.packageName, Function.identity()));
+        mActivePackagesCacheLatch.countDown();
+        Slog.i(TAG, "Finished parsing apex files in " + (SystemClock.currentTimeMicro()
+                - startTimeMicros) + " μs");
     }
 
     /**
      * Retrieves information about an active APEX package.
      *
+     * <p>This method blocks caller thread until {@link #parseApexFiles()} succeeds. Note that in
+     * case {@link #parseApexFiles()}} throws an exception this method will never finish
+     * essentially putting device into a boot loop.
+     *
      * @param packageName the package name to look for. Note that this is the package name reported
      *                    in the APK container manifest (i.e. AndroidManifest.xml), which might
      *                    differ from the one reported in the APEX manifest (i.e.
@@ -123,30 +155,43 @@
      *         is not found.
      */
     @Nullable PackageInfo getActivePackage(String packageName) {
-        populateActivePackagesCacheIfNeeded();
+        waitForLatch(mActivePackagesCacheLatch);
         return mActivePackagesCache.get(packageName);
     }
 
     /**
      * Retrieves information about all active APEX packages.
      *
+     * <p>This method blocks caller thread until {@link #parseApexFiles()} succeeds. Note that in
+     * case {@link #parseApexFiles()}} throws an exception this method will never finish
+     * essentially putting device into a boot loop.
+     *
      * @return a Collection of PackageInfo object, each one containing information about a different
      *         active package.
      */
     Collection<PackageInfo> getActivePackages() {
-        populateActivePackagesCacheIfNeeded();
+        waitForLatch(mActivePackagesCacheLatch);
         return mActivePackagesCache.values();
     }
 
     /**
      * Checks if {@code packageName} is an apex package.
      *
+     * <p>This method blocks caller thread until {@link #populateApexFilesCache()} succeeds. Note
+     * that in case {@link #populateApexFilesCache()} throws an exception this method will never
+     * finish essentially putting device into a boot loop.
+     *
      * @param packageName package to check.
      * @return {@code true} if {@code packageName} is an apex package.
      */
     boolean isApexPackage(String packageName) {
-        populateActivePackagesCacheIfNeeded();
-        return mActivePackagesCache.containsKey(packageName);
+        waitForLatch(mApexFilesCacheLatch);
+        for (ApexInfo ai : mApexFiles) {
+            if (ai.packageName.equals(packageName)) {
+                return true;
+            }
+        }
+        return false;
     }
 
     /**
@@ -274,6 +319,19 @@
     }
 
     /**
+     * Blocks current thread until {@code latch} has counted down to zero.
+     *
+     * @throws RuntimeException if thread was interrupted while waiting.
+     */
+    private void waitForLatch(CountDownLatch latch) {
+        try {
+            latch.await();
+        } catch (InterruptedException e) {
+            throw new RuntimeException("Interrupted waiting for cache to be populated", e);
+        }
+    }
+
+    /**
      * Dumps various state information to the provided {@link PrintWriter} object.
      *
      * @param pw the {@link PrintWriter} object to send information to.
@@ -286,7 +344,7 @@
         ipw.println("Active APEX packages:");
         ipw.increaseIndent();
         try {
-            populateActivePackagesCacheIfNeeded();
+            waitForLatch(mActivePackagesCacheLatch);
             for (PackageInfo pi : mActivePackagesCache.values()) {
                 if (packageName != null && !packageName.equals(pi.packageName)) {
                     continue;
@@ -331,8 +389,4 @@
             ipw.println("Couldn't communicate with apexd.");
         }
     }
-
-    public void onBootCompleted() {
-        populateActivePackagesCacheIfNeeded();
-    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 74fb4b2..5f6e739 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2050,6 +2050,13 @@
         synchronized (mLock) {
             assertCallerIsOwnerOrRootLocked();
 
+            if (isStagedAndInTerminalState()) {
+                // We keep the session in the database if it's in a finalized state. It will be
+                // removed by PackageInstallerService when the last update time is old enough.
+                // Also, in such cases cleanStageDir() has already been executed so no need to
+                // do it now.
+                return;
+            }
             if (mCommitted && params.isStaged) {
                 synchronized (mLock) {
                     mDestroyed = true;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index e08af6f..20d47ed 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -939,6 +939,7 @@
     ComponentName mCustomResolverComponentName;
 
     boolean mResolverReplaced = false;
+    boolean mOkToReplacePersistentPackages = false;
 
     private final @Nullable ComponentName mIntentFilterVerifierComponent;
     private final @Nullable IntentFilterVerifier<ActivityIntentInfo> mIntentFilterVerifier;
@@ -2374,6 +2375,8 @@
 
     public PackageManagerService(Context context, Installer installer,
             boolean factoryTest, boolean onlyCore) {
+        mApexManager = LocalServices.getService(ApexManager.class);
+
         LockGuard.installLock(mPackages, LockGuard.INDEX_PACKAGES);
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "create package manager");
         EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,
@@ -2470,7 +2473,6 @@
 
         mProtectedPackages = new ProtectedPackages(mContext);
 
-        mApexManager = new ApexManager(context);
         synchronized (mInstallLock) {
         // writer
         synchronized (mPackages) {
@@ -17324,7 +17326,8 @@
                                         + " target SDK " + oldTargetSdk + " does.");
                     }
                     // Prevent persistent apps from being updated
-                    if ((oldPackage.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) != 0) {
+                    if (((oldPackage.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) != 0)
+                            && !mOkToReplacePersistentPackages) {
                         throw new PrepareFailure(PackageManager.INSTALL_FAILED_INVALID_APK,
                                 "Package " + oldPackage.packageName + " is a persistent app. "
                                         + "Persistent apps are not updateable.");
@@ -21462,7 +21465,6 @@
         storage.registerListener(mStorageListener);
 
         mInstallerService.systemReady();
-        mApexManager.systemReady();
         mPackageDexOptimizer.systemReady();
 
         getStorageManagerInternal().addExternalStoragePolicy(
@@ -21505,10 +21507,12 @@
 
         mModuleInfoProvider.systemReady();
 
+        mOkToReplacePersistentPackages = true;
         // Installer service might attempt to install some packages that have been staged for
         // installation on reboot. Make sure this is the last component to be call since the
         // installation might require other components to be ready.
         mInstallerService.restoreAndApplyStagedSessionIfNeeded();
+        mOkToReplacePersistentPackages = false;
     }
 
     public void waitForAppDataPrepared() {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a55ee5f..7bc9600 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5810,10 +5810,8 @@
      */
     Intent getSecondaryHomeIntent(String preferredPackage) {
         final Intent intent = new Intent(mTopAction, mTopData != null ? Uri.parse(mTopData) : null);
-        final boolean useSystemProvidedLauncher = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_useSystemProvidedLauncherForSecondary);
-        if (preferredPackage == null || useSystemProvidedLauncher) {
-            // Using the component stored in config if no package name or forced.
+        if (preferredPackage == null) {
+            // Using the component stored in config if no package name.
             final String secondaryHomeComponent = mContext.getResources().getString(
                     com.android.internal.R.string.config_secondaryHomeComponent);
             intent.setComponent(ComponentName.unflattenFromString(secondaryHomeComponent));
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 7bc6776..4d37f1a 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -1860,13 +1860,14 @@
      * Fail if the main interface fails to initialize
      */
     if (gnssHal == nullptr) {
-        ALOGE("Unable to Initialize GNSS HAL\n");
+        ALOGE("Unable to initialize GNSS HAL.");
         return JNI_FALSE;
     }
 
-    sp<IGnssCallback> gnssCbIface = new GnssCallback();
-
     Return<bool> result = false;
+
+    // Set top level IGnss.hal callback.
+    sp<IGnssCallback> gnssCbIface = new GnssCallback();
     if (gnssHal_V2_0 != nullptr) {
         result = gnssHal_V2_0->setCallback_2_0(gnssCbIface);
     } else if (gnssHal_V1_1 != nullptr) {
@@ -1876,62 +1877,89 @@
     }
 
     if (!result.isOk() || !result) {
-        ALOGE("SetCallback for Gnss Interface fails\n");
+        ALOGE("SetCallback for IGnss interface failed.");
         return JNI_FALSE;
     }
 
-    sp<IGnssXtraCallback> gnssXtraCbIface = new GnssXtraCallback();
+    // Set IGnssXtra.hal callback.
     if (gnssXtraIface == nullptr) {
-        ALOGI("Unable to initialize GNSS Xtra interface\n");
+        ALOGI("Unable to initialize IGnssXtra interface.");
     } else {
+        sp<IGnssXtraCallback> gnssXtraCbIface = new GnssXtraCallback();
         result = gnssXtraIface->setCallback(gnssXtraCbIface);
         if (!result.isOk() || !result) {
             gnssXtraIface = nullptr;
-            ALOGI("SetCallback for Gnss Xtra Interface fails\n");
+            ALOGI("SetCallback for IGnssXtra interface failed.");
         }
     }
 
+    // Set IAGnss.hal callback.
+    Return<void> agnssStatus;
     if (agnssIface_V2_0 != nullptr) {
         sp<IAGnssCallback_V2_0> aGnssCbIface = new AGnssCallback_V2_0();
-        agnssIface_V2_0->setCallback(aGnssCbIface);
+        agnssStatus = agnssIface_V2_0->setCallback(aGnssCbIface);
     } else if (agnssIface != nullptr) {
         sp<IAGnssCallback_V1_0> aGnssCbIface = new AGnssCallback_V1_0();
-        agnssIface->setCallback(aGnssCbIface);
+        agnssStatus = agnssIface->setCallback(aGnssCbIface);
     } else {
-        ALOGI("Unable to initialize AGnss interface\n");
+        ALOGI("Unable to initialize IAGnss interface.");
     }
 
+    if (!agnssStatus.isOk()) {
+        ALOGI("SetCallback for IAGnss interface failed.");
+    }
+
+    // Set IGnssGeofencing.hal callback.
     sp<IGnssGeofenceCallback> gnssGeofencingCbIface = new GnssGeofenceCallback();
     if (gnssGeofencingIface != nullptr) {
-      gnssGeofencingIface->setCallback(gnssGeofencingCbIface);
+        auto status = gnssGeofencingIface->setCallback(gnssGeofencingCbIface);
+        if (!status.isOk()) {
+            ALOGI("SetCallback for IGnssGeofencing interface failed.");
+        }
     } else {
-        ALOGI("Unable to initialize GNSS Geofencing interface\n");
+        ALOGI("Unable to initialize IGnssGeofencing interface.");
     }
 
+    // Set IGnssNi.hal callback.
     sp<IGnssNiCallback> gnssNiCbIface = new GnssNiCallback();
     if (gnssNiIface != nullptr) {
-        gnssNiIface->setCallback(gnssNiCbIface);
+        auto status = gnssNiIface->setCallback(gnssNiCbIface);
+        if (!status.isOk()) {
+            ALOGI("SetCallback for IGnssNi interface failed.");
+        }
     } else {
-        ALOGI("Unable to initialize GNSS NI interface\n");
+        ALOGI("Unable to initialize IGnssNi interface.");
     }
 
+    // Set IAGnssRil.hal callback.
     sp<IAGnssRilCallback> aGnssRilCbIface = new AGnssRilCallback();
     if (agnssRilIface != nullptr) {
-        agnssRilIface->setCallback(aGnssRilCbIface);
+        auto status = agnssRilIface->setCallback(aGnssRilCbIface);
+        if (!status.isOk()) {
+            ALOGI("SetCallback for IAGnssRil interface failed.");
+        }
     } else {
-        ALOGI("Unable to initialize AGnss Ril interface\n");
+        ALOGI("Unable to initialize IAGnssRil interface.");
     }
 
+    // Set IGnssVisibilityControl.hal callback.
     if (gnssVisibilityControlIface != nullptr) {
         sp<IGnssVisibilityControlCallback> gnssVisibilityControlCbIface =
                 new GnssVisibilityControlCallback();
-        gnssVisibilityControlIface->setCallback(gnssVisibilityControlCbIface);
+        result = gnssVisibilityControlIface->setCallback(gnssVisibilityControlCbIface);
+        if (!result.isOk() || !result) {
+            ALOGI("SetCallback for IGnssVisibilityControl interface failed.");
+        }
     }
 
+    // Set IMeasurementCorrections.hal callback.
     if (gnssCorrectionsIface != nullptr) {
         sp<IMeasurementCorrectionsCallback> gnssCorrectionsIfaceCbIface =
                 new MeasurementCorrectionsCallback();
-        gnssCorrectionsIface->setCallback(gnssCorrectionsIfaceCbIface);
+        result = gnssCorrectionsIface->setCallback(gnssCorrectionsIfaceCbIface);
+        if (!result.isOk() || !result) {
+            ALOGI("SetCallback for IMeasurementCorrections interface failed.");
+        }
     }
 
     return JNI_TRUE;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index be7dd31..4ac8342 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -115,6 +115,7 @@
 import com.android.server.os.BugreportManagerService;
 import com.android.server.os.DeviceIdentifiersPolicyService;
 import com.android.server.os.SchedulingPolicyService;
+import com.android.server.pm.ApexManager;
 import com.android.server.pm.BackgroundDexOptService;
 import com.android.server.pm.CrossProfileAppsService;
 import com.android.server.pm.DynamicCodeLoggingService;
@@ -627,6 +628,12 @@
         watchdog.start();
         traceEnd();
 
+        // Start ApexManager as early as we can to give it enough time to call apexd and populate
+        // cache of known apex packages. Note that calling apexd will happen asynchronously.
+        traceBeginAndSlog("StartApexManager");
+        mSystemServiceManager.startService(ApexManager.class);
+        traceEnd();
+
         Slog.i(TAG, "Reading configuration...");
         final String TAG_SYSTEM_CONFIG = "ReadingSystemConfig";
         traceBeginAndSlog(TAG_SYSTEM_CONFIG);
diff --git a/services/net/Android.bp b/services/net/Android.bp
index a44d835..ab11fe4 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -56,6 +56,7 @@
     versions: [
         "1",
         "2",
+        "3",
     ],
 }
 
diff --git a/services/net/aidl/networkstack/3/android/net/DhcpResultsParcelable.aidl b/services/net/aidl/networkstack/3/android/net/DhcpResultsParcelable.aidl
new file mode 100644
index 0000000..31891de
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/DhcpResultsParcelable.aidl
@@ -0,0 +1,9 @@
+package android.net;
+parcelable DhcpResultsParcelable {
+  android.net.StaticIpConfiguration baseConfiguration;
+  int leaseDuration;
+  int mtu;
+  String serverAddress;
+  String vendorInfo;
+  String serverHostName;
+}
diff --git a/services/net/aidl/networkstack/3/android/net/INetworkMonitor.aidl b/services/net/aidl/networkstack/3/android/net/INetworkMonitor.aidl
new file mode 100644
index 0000000..029968b
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/INetworkMonitor.aidl
@@ -0,0 +1,24 @@
+package android.net;
+interface INetworkMonitor {
+  oneway void start();
+  oneway void launchCaptivePortalApp();
+  oneway void notifyCaptivePortalAppFinished(int response);
+  oneway void setAcceptPartialConnectivity();
+  oneway void forceReevaluation(int uid);
+  oneway void notifyPrivateDnsChanged(in android.net.PrivateDnsConfigParcel config);
+  oneway void notifyDnsResponse(int returnCode);
+  oneway void notifyNetworkConnected(in android.net.LinkProperties lp, in android.net.NetworkCapabilities nc);
+  oneway void notifyNetworkDisconnected();
+  oneway void notifyLinkPropertiesChanged(in android.net.LinkProperties lp);
+  oneway void notifyNetworkCapabilitiesChanged(in android.net.NetworkCapabilities nc);
+  const int NETWORK_TEST_RESULT_VALID = 0;
+  const int NETWORK_TEST_RESULT_INVALID = 1;
+  const int NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY = 2;
+  const int NETWORK_VALIDATION_RESULT_VALID = 1;
+  const int NETWORK_VALIDATION_RESULT_PARTIAL = 2;
+  const int NETWORK_VALIDATION_PROBE_DNS = 4;
+  const int NETWORK_VALIDATION_PROBE_HTTP = 8;
+  const int NETWORK_VALIDATION_PROBE_HTTPS = 16;
+  const int NETWORK_VALIDATION_PROBE_FALLBACK = 32;
+  const int NETWORK_VALIDATION_PROBE_PRIVDNS = 64;
+}
diff --git a/services/net/aidl/networkstack/3/android/net/INetworkMonitorCallbacks.aidl b/services/net/aidl/networkstack/3/android/net/INetworkMonitorCallbacks.aidl
new file mode 100644
index 0000000..ee9871d
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/INetworkMonitorCallbacks.aidl
@@ -0,0 +1,8 @@
+package android.net;
+interface INetworkMonitorCallbacks {
+  oneway void onNetworkMonitorCreated(in android.net.INetworkMonitor networkMonitor);
+  oneway void notifyNetworkTested(int testResult, @nullable String redirectUrl);
+  oneway void notifyPrivateDnsConfigResolved(in android.net.PrivateDnsConfigParcel config);
+  oneway void showProvisioningNotification(String action, String packageName);
+  oneway void hideProvisioningNotification();
+}
diff --git a/services/net/aidl/networkstack/3/android/net/INetworkStackConnector.aidl b/services/net/aidl/networkstack/3/android/net/INetworkStackConnector.aidl
new file mode 100644
index 0000000..7da11e4
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/INetworkStackConnector.aidl
@@ -0,0 +1,7 @@
+package android.net;
+interface INetworkStackConnector {
+  oneway void makeDhcpServer(in String ifName, in android.net.dhcp.DhcpServingParamsParcel params, in android.net.dhcp.IDhcpServerCallbacks cb);
+  oneway void makeNetworkMonitor(in android.net.Network network, String name, in android.net.INetworkMonitorCallbacks cb);
+  oneway void makeIpClient(in String ifName, in android.net.ip.IIpClientCallbacks callbacks);
+  oneway void fetchIpMemoryStore(in android.net.IIpMemoryStoreCallbacks cb);
+}
diff --git a/services/net/aidl/networkstack/3/android/net/INetworkStackStatusCallback.aidl b/services/net/aidl/networkstack/3/android/net/INetworkStackStatusCallback.aidl
new file mode 100644
index 0000000..f6ca6f7
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/INetworkStackStatusCallback.aidl
@@ -0,0 +1,4 @@
+package android.net;
+interface INetworkStackStatusCallback {
+  oneway void onStatusAvailable(int statusCode);
+}
diff --git a/services/net/aidl/networkstack/3/android/net/InitialConfigurationParcelable.aidl b/services/net/aidl/networkstack/3/android/net/InitialConfigurationParcelable.aidl
new file mode 100644
index 0000000..c80a787
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/InitialConfigurationParcelable.aidl
@@ -0,0 +1,7 @@
+package android.net;
+parcelable InitialConfigurationParcelable {
+  android.net.LinkAddress[] ipAddresses;
+  android.net.IpPrefix[] directlyConnectedRoutes;
+  String[] dnsServers;
+  String gateway;
+}
diff --git a/services/net/aidl/networkstack/3/android/net/NattKeepalivePacketDataParcelable.aidl b/services/net/aidl/networkstack/3/android/net/NattKeepalivePacketDataParcelable.aidl
new file mode 100644
index 0000000..65de883
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/NattKeepalivePacketDataParcelable.aidl
@@ -0,0 +1,7 @@
+package android.net;
+parcelable NattKeepalivePacketDataParcelable {
+  byte[] srcAddress;
+  int srcPort;
+  byte[] dstAddress;
+  int dstPort;
+}
diff --git a/services/net/aidl/networkstack/3/android/net/PrivateDnsConfigParcel.aidl b/services/net/aidl/networkstack/3/android/net/PrivateDnsConfigParcel.aidl
new file mode 100644
index 0000000..2de790b
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/PrivateDnsConfigParcel.aidl
@@ -0,0 +1,5 @@
+package android.net;
+parcelable PrivateDnsConfigParcel {
+  String hostname;
+  String[] ips;
+}
diff --git a/services/net/aidl/networkstack/3/android/net/ProvisioningConfigurationParcelable.aidl b/services/net/aidl/networkstack/3/android/net/ProvisioningConfigurationParcelable.aidl
new file mode 100644
index 0000000..3a6c304
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/ProvisioningConfigurationParcelable.aidl
@@ -0,0 +1,15 @@
+package android.net;
+parcelable ProvisioningConfigurationParcelable {
+  boolean enableIPv4;
+  boolean enableIPv6;
+  boolean usingMultinetworkPolicyTracker;
+  boolean usingIpReachabilityMonitor;
+  int requestedPreDhcpActionMs;
+  android.net.InitialConfigurationParcelable initialConfig;
+  android.net.StaticIpConfiguration staticIpConfig;
+  android.net.apf.ApfCapabilities apfCapabilities;
+  int provisioningTimeoutMs;
+  int ipv6AddrGenMode;
+  android.net.Network network;
+  String displayName;
+}
diff --git a/services/net/aidl/networkstack/3/android/net/TcpKeepalivePacketDataParcelable.aidl b/services/net/aidl/networkstack/3/android/net/TcpKeepalivePacketDataParcelable.aidl
new file mode 100644
index 0000000..e121c06
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/TcpKeepalivePacketDataParcelable.aidl
@@ -0,0 +1,13 @@
+package android.net;
+parcelable TcpKeepalivePacketDataParcelable {
+  byte[] srcAddress;
+  int srcPort;
+  byte[] dstAddress;
+  int dstPort;
+  int seq;
+  int ack;
+  int rcvWnd;
+  int rcvWndScale;
+  int tos;
+  int ttl;
+}
diff --git a/services/net/aidl/networkstack/3/android/net/dhcp/DhcpServingParamsParcel.aidl b/services/net/aidl/networkstack/3/android/net/dhcp/DhcpServingParamsParcel.aidl
new file mode 100644
index 0000000..67193ae
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/dhcp/DhcpServingParamsParcel.aidl
@@ -0,0 +1,11 @@
+package android.net.dhcp;
+parcelable DhcpServingParamsParcel {
+  int serverAddr;
+  int serverAddrPrefixLength;
+  int[] defaultRouters;
+  int[] dnsServers;
+  int[] excludedAddrs;
+  long dhcpLeaseTimeSecs;
+  int linkMtu;
+  boolean metered;
+}
diff --git a/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServer.aidl b/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServer.aidl
new file mode 100644
index 0000000..9143158
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServer.aidl
@@ -0,0 +1,10 @@
+package android.net.dhcp;
+interface IDhcpServer {
+  oneway void start(in android.net.INetworkStackStatusCallback cb);
+  oneway void updateParams(in android.net.dhcp.DhcpServingParamsParcel params, in android.net.INetworkStackStatusCallback cb);
+  oneway void stop(in android.net.INetworkStackStatusCallback cb);
+  const int STATUS_UNKNOWN = 0;
+  const int STATUS_SUCCESS = 1;
+  const int STATUS_INVALID_ARGUMENT = 2;
+  const int STATUS_UNKNOWN_ERROR = 3;
+}
diff --git a/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServerCallbacks.aidl b/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServerCallbacks.aidl
new file mode 100644
index 0000000..dcc4489
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServerCallbacks.aidl
@@ -0,0 +1,4 @@
+package android.net.dhcp;
+interface IDhcpServerCallbacks {
+  oneway void onDhcpServerCreated(int statusCode, in android.net.dhcp.IDhcpServer server);
+}
diff --git a/services/net/aidl/networkstack/3/android/net/ip/IIpClient.aidl b/services/net/aidl/networkstack/3/android/net/ip/IIpClient.aidl
new file mode 100644
index 0000000..176a5ce
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/ip/IIpClient.aidl
@@ -0,0 +1,16 @@
+package android.net.ip;
+interface IIpClient {
+  oneway void completedPreDhcpAction();
+  oneway void confirmConfiguration();
+  oneway void readPacketFilterComplete(in byte[] data);
+  oneway void shutdown();
+  oneway void startProvisioning(in android.net.ProvisioningConfigurationParcelable req);
+  oneway void stop();
+  oneway void setTcpBufferSizes(in String tcpBufferSizes);
+  oneway void setHttpProxy(in android.net.ProxyInfo proxyInfo);
+  oneway void setMulticastFilter(boolean enabled);
+  oneway void addKeepalivePacketFilter(int slot, in android.net.TcpKeepalivePacketDataParcelable pkt);
+  oneway void removeKeepalivePacketFilter(int slot);
+  oneway void setL2KeyAndGroupHint(in String l2Key, in String groupHint);
+  oneway void addNattKeepalivePacketFilter(int slot, in android.net.NattKeepalivePacketDataParcelable pkt);
+}
diff --git a/services/net/aidl/networkstack/3/android/net/ip/IIpClientCallbacks.aidl b/services/net/aidl/networkstack/3/android/net/ip/IIpClientCallbacks.aidl
new file mode 100644
index 0000000..d6bc808
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/ip/IIpClientCallbacks.aidl
@@ -0,0 +1,16 @@
+package android.net.ip;
+interface IIpClientCallbacks {
+  oneway void onIpClientCreated(in android.net.ip.IIpClient ipClient);
+  oneway void onPreDhcpAction();
+  oneway void onPostDhcpAction();
+  oneway void onNewDhcpResults(in android.net.DhcpResultsParcelable dhcpResults);
+  oneway void onProvisioningSuccess(in android.net.LinkProperties newLp);
+  oneway void onProvisioningFailure(in android.net.LinkProperties newLp);
+  oneway void onLinkPropertiesChange(in android.net.LinkProperties newLp);
+  oneway void onReachabilityLost(in String logMsg);
+  oneway void onQuit();
+  oneway void installPacketFilter(in byte[] filter);
+  oneway void startReadPacketFilter();
+  oneway void setFallbackMulticastFilter(boolean enabled);
+  oneway void setNeighborDiscoveryOffload(boolean enable);
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
new file mode 100644
index 0000000..f7edf65
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -0,0 +1,437 @@
+/*
+ * 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.job;
+
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.IActivityManager;
+import android.app.job.JobInfo;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.NetworkPolicyManager;
+import android.os.BatteryManagerInternal;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.SystemClock;
+
+import com.android.server.AppStateTracker;
+import com.android.server.DeviceIdleController;
+import com.android.server.LocalServices;
+import com.android.server.job.controllers.JobStatus;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.ZoneOffset;
+
+public class JobSchedulerServiceTest {
+    private JobSchedulerService mService;
+
+    private MockitoSession mMockingSession;
+    @Mock
+    private ActivityManagerInternal mActivityMangerInternal;
+    @Mock
+    private Context mContext;
+
+    private class TestJobSchedulerService extends JobSchedulerService {
+        TestJobSchedulerService(Context context) {
+            super(context);
+        }
+
+        @Override
+        public boolean isChainedAttributionEnabled() {
+            return false;
+        }
+    }
+
+    @Before
+    public void setUp() {
+        mMockingSession = mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.LENIENT)
+                .mockStatic(LocalServices.class)
+                .startMocking();
+
+        // Called in JobSchedulerService constructor.
+        when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
+        doReturn(mActivityMangerInternal)
+                .when(() -> LocalServices.getService(ActivityManagerInternal.class));
+        doReturn(mock(UsageStatsManagerInternal.class))
+                .when(() -> LocalServices.getService(UsageStatsManagerInternal.class));
+        // Called in BackgroundJobsController constructor.
+        doReturn(mock(AppStateTracker.class))
+                .when(() -> LocalServices.getService(AppStateTracker.class));
+        // Called in BatteryController constructor.
+        doReturn(mock(BatteryManagerInternal.class))
+                .when(() -> LocalServices.getService(BatteryManagerInternal.class));
+        // Called in ConnectivityController constructor.
+        when(mContext.getSystemService(ConnectivityManager.class))
+                .thenReturn(mock(ConnectivityManager.class));
+        when(mContext.getSystemService(NetworkPolicyManager.class))
+                .thenReturn(mock(NetworkPolicyManager.class));
+        // Called in DeviceIdleJobsController constructor.
+        doReturn(mock(DeviceIdleController.LocalService.class))
+                .when(() -> LocalServices.getService(DeviceIdleController.LocalService.class));
+        // Used in JobStatus.
+        doReturn(mock(PackageManagerInternal.class))
+                .when(() -> LocalServices.getService(PackageManagerInternal.class));
+        // Called via IdleController constructor.
+        when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
+        when(mContext.getResources()).thenReturn(mock(Resources.class));
+        // Called in QuotaController constructor.
+        IActivityManager activityManager = ActivityManager.getService();
+        spyOn(activityManager);
+        try {
+            doNothing().when(activityManager).registerUidObserver(any(), anyInt(), anyInt(), any());
+        } catch (RemoteException e) {
+            fail("registerUidObserver threw exception: " + e.getMessage());
+        }
+
+        JobSchedulerService.sSystemClock = Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
+
+        mService = new TestJobSchedulerService(mContext);
+    }
+
+    @After
+    public void tearDown() {
+        if (mMockingSession != null) {
+            mMockingSession.finishMocking();
+        }
+    }
+
+    private Clock getAdvancedClock(Clock clock, long incrementMs) {
+        return Clock.offset(clock, Duration.ofMillis(incrementMs));
+    }
+
+    private void advanceElapsedClock(long incrementMs) {
+        JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock(
+                JobSchedulerService.sElapsedRealtimeClock, incrementMs);
+    }
+
+    private static JobInfo.Builder createJobInfo() {
+        return new JobInfo.Builder(351, new ComponentName("foo", "bar"));
+    }
+
+    private JobStatus createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder) {
+        return JobStatus.createFromJobInfo(
+                jobInfoBuilder.build(), 1234, "com.android.test", 0, testTag);
+    }
+
+    /**
+     * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
+     * with the correct delay and deadline constraints if the periodic job is completed and
+     * rescheduled while run in its expected running window.
+     */
+    @Test
+    public void testGetRescheduleJobForPeriodic_insideWindow() {
+        final long now = sElapsedRealtimeClock.millis();
+        JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow",
+                createJobInfo().setPeriodic(HOUR_IN_MILLIS));
+        final long nextWindowStartTime = now + HOUR_IN_MILLIS;
+        final long nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
+
+        JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        advanceElapsedClock(10 * MINUTE_IN_MILLIS); // now + 10 minutes
+
+        rescheduledJob = mService.getRescheduleJobForPeriodic(job);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        advanceElapsedClock(45 * MINUTE_IN_MILLIS); // now + 55 minutes
+
+        rescheduledJob = mService.getRescheduleJobForPeriodic(job);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        advanceElapsedClock(4 * MINUTE_IN_MILLIS); // now + 59 minutes
+
+        rescheduledJob = mService.getRescheduleJobForPeriodic(job);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+    }
+
+    /**
+     * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
+     * with the correct delay and deadline constraints if the periodic job with a custom flex
+     * setting is completed and rescheduled while run in its expected running window.
+     */
+    @Test
+    public void testGetRescheduleJobForPeriodic_insideWindow_flex() {
+        JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow_flex",
+                createJobInfo().setPeriodic(HOUR_IN_MILLIS, 30 * MINUTE_IN_MILLIS));
+        // First window starts 30 minutes from now.
+        advanceElapsedClock(30 * MINUTE_IN_MILLIS);
+        final long now = sElapsedRealtimeClock.millis();
+        final long nextWindowStartTime = now + HOUR_IN_MILLIS;
+        final long nextWindowEndTime = now + 90 * MINUTE_IN_MILLIS;
+
+        JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        advanceElapsedClock(10 * MINUTE_IN_MILLIS); // now + 10 minutes
+
+        rescheduledJob = mService.getRescheduleJobForPeriodic(job);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        advanceElapsedClock(15 * MINUTE_IN_MILLIS); // now + 25 minutes
+
+        rescheduledJob = mService.getRescheduleJobForPeriodic(job);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        advanceElapsedClock(4 * MINUTE_IN_MILLIS); // now + 29 minutes
+
+        rescheduledJob = mService.getRescheduleJobForPeriodic(job);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+    }
+
+    /**
+     * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
+     * with the correct delay and deadline constraints if the periodic job failed but then ran
+     * successfully and was rescheduled while run in its expected running window.
+     */
+    @Test
+    public void testGetRescheduleJobForPeriodic_insideWindow_failedJob() {
+        final long now = sElapsedRealtimeClock.millis();
+        final long nextWindowStartTime = now + HOUR_IN_MILLIS;
+        final long nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
+        JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow_failedJob",
+                createJobInfo().setPeriodic(HOUR_IN_MILLIS));
+        JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job);
+
+        JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        advanceElapsedClock(5 * MINUTE_IN_MILLIS); // now + 5 minutes
+        failedJob = mService.getRescheduleJobForFailureLocked(job);
+        advanceElapsedClock(5 * MINUTE_IN_MILLIS); // now + 10 minutes
+
+        rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        advanceElapsedClock(35 * MINUTE_IN_MILLIS); // now + 45 minutes
+        failedJob = mService.getRescheduleJobForFailureLocked(job);
+        advanceElapsedClock(10 * MINUTE_IN_MILLIS); // now + 55 minutes
+
+        rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 57 minutes
+        failedJob = mService.getRescheduleJobForFailureLocked(job);
+        advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 59 minutes
+
+        rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+    }
+
+    /**
+     * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
+     * with the correct delay and deadline constraints if the periodic job is completed and
+     * rescheduled when run after its expected running window.
+     */
+    @Test
+    public void testGetRescheduleJobForPeriodic_outsideWindow() {
+        JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_outsideWindow",
+                createJobInfo().setPeriodic(HOUR_IN_MILLIS));
+        long now = sElapsedRealtimeClock.millis();
+        long nextWindowStartTime = now + HOUR_IN_MILLIS;
+        long nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
+
+        advanceElapsedClock(HOUR_IN_MILLIS + MINUTE_IN_MILLIS);
+        // Say the job ran at the very end of its previous window. The intended JSS behavior is to
+        // have consistent windows, so the new window should start as soon as the previous window
+        // ended and end PERIOD time after the previous window ended.
+        JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        advanceElapsedClock(2 * HOUR_IN_MILLIS);
+        // Say that the job ran at this point, possibly due to device idle.
+        // The next window should be consistent (start and end at the time it would have had the job
+        // run normally in previous windows).
+        nextWindowStartTime += 2 * HOUR_IN_MILLIS;
+        nextWindowEndTime += 2 * HOUR_IN_MILLIS;
+
+        rescheduledJob = mService.getRescheduleJobForPeriodic(job);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+    }
+
+    /**
+     * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
+     * with the correct delay and deadline constraints if the periodic job with a custom flex
+     * setting is completed and rescheduled when run after its expected running window.
+     */
+    @Test
+    public void testGetRescheduleJobForPeriodic_outsideWindow_flex() {
+        JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_outsideWindow_flex",
+                createJobInfo().setPeriodic(HOUR_IN_MILLIS, 30 * MINUTE_IN_MILLIS));
+        // First window starts 30 minutes from now.
+        advanceElapsedClock(30 * MINUTE_IN_MILLIS);
+        long now = sElapsedRealtimeClock.millis();
+        long nextWindowStartTime = now + HOUR_IN_MILLIS;
+        long nextWindowEndTime = now + 90 * MINUTE_IN_MILLIS;
+
+        advanceElapsedClock(31 * MINUTE_IN_MILLIS);
+        // Say the job ran at the very end of its previous window. The intended JSS behavior is to
+        // have consistent windows, so the new window should start as soon as the previous window
+        // ended and end PERIOD time after the previous window ended.
+        JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        // 5 minutes before the start of the next window. It's too close to the next window, so the
+        // returned job should be for the window after.
+        advanceElapsedClock(24 * MINUTE_IN_MILLIS);
+        nextWindowStartTime += HOUR_IN_MILLIS;
+        nextWindowEndTime += HOUR_IN_MILLIS;
+        rescheduledJob = mService.getRescheduleJobForPeriodic(job);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        advanceElapsedClock(2 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS);
+        // Say that the job ran at this point, possibly due to device idle.
+        // The next window should be consistent (start and end at the time it would have had the job
+        // run normally in previous windows).
+        nextWindowStartTime += 2 * HOUR_IN_MILLIS;
+        nextWindowEndTime += 2 * HOUR_IN_MILLIS;
+
+        rescheduledJob = mService.getRescheduleJobForPeriodic(job);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+    }
+
+    /**
+     * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
+     * with the correct delay and deadline constraints if the periodic job failed but then ran
+     * successfully and was rescheduled when run after its expected running window.
+     */
+    @Test
+    public void testGetRescheduleJobForPeriodic_outsideWindow_failedJob() {
+        JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_outsideWindow_failedJob",
+                createJobInfo().setPeriodic(HOUR_IN_MILLIS));
+        JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job);
+        long now = sElapsedRealtimeClock.millis();
+        long nextWindowStartTime = now + HOUR_IN_MILLIS;
+        long nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
+
+        advanceElapsedClock(HOUR_IN_MILLIS + MINUTE_IN_MILLIS);
+        // Say the job ran at the very end of its previous window. The intended JSS behavior is to
+        // have consistent windows, so the new window should start as soon as the previous window
+        // ended and end PERIOD time after the previous window ended.
+        JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        advanceElapsedClock(2 * HOUR_IN_MILLIS);
+        // Say that the job ran at this point, possibly due to device idle.
+        // The next window should be consistent (start and end at the time it would have had the job
+        // run normally in previous windows).
+        nextWindowStartTime += 2 * HOUR_IN_MILLIS;
+        nextWindowEndTime += 2 * HOUR_IN_MILLIS;
+
+        rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+    }
+
+    /**
+     * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
+     * with the correct delay and deadline constraints if the periodic job with a custom flex
+     * setting failed but then ran successfully and was rescheduled when run after its expected
+     * running window.
+     */
+    @Test
+    public void testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob() {
+        JobStatus job = createJobStatus(
+                "testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob",
+                createJobInfo().setPeriodic(HOUR_IN_MILLIS, 30 * MINUTE_IN_MILLIS));
+        JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job);
+        // First window starts 30 minutes from now.
+        advanceElapsedClock(30 * MINUTE_IN_MILLIS);
+        long now = sElapsedRealtimeClock.millis();
+        long nextWindowStartTime = now + HOUR_IN_MILLIS;
+        long nextWindowEndTime = now + 90 * MINUTE_IN_MILLIS;
+
+        advanceElapsedClock(31 * MINUTE_IN_MILLIS);
+        // Say the job ran at the very end of its previous window. The intended JSS behavior is to
+        // have consistent windows, so the new window should start as soon as the previous window
+        // ended and end PERIOD time after the previous window ended.
+        JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        // 5 minutes before the start of the next window. It's too close to the next window, so the
+        // returned job should be for the window after.
+        advanceElapsedClock(24 * MINUTE_IN_MILLIS);
+        nextWindowStartTime += HOUR_IN_MILLIS;
+        nextWindowEndTime += HOUR_IN_MILLIS;
+        rescheduledJob = mService.getRescheduleJobForPeriodic(job);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+
+        advanceElapsedClock(2 * HOUR_IN_MILLIS);
+        // Say that the job ran at this point, possibly due to device idle.
+        // The next window should be consistent (start and end at the time it would have had the job
+        // run normally in previous windows).
+        nextWindowStartTime += 2 * HOUR_IN_MILLIS;
+        nextWindowEndTime += 2 * HOUR_IN_MILLIS;
+
+        rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
+        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
+        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 99b827c..bdc46ec 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -263,6 +263,7 @@
     private static final int INVALID_CARRIER_CONFIG_VALUE = -9999;
     private long mDefaultWarningBytes; // filled in with the actual default before tests are run
     private long mDefaultLimitBytes; // filled in with the actual default before tests are run
+    private PersistableBundle mCarrierConfig = CarrierConfigManager.getDefaultConfig();
 
     private static final int APP_ID_A = android.os.Process.FIRST_APPLICATION_UID + 4;
     private static final int APP_ID_B = android.os.Process.FIRST_APPLICATION_UID + 8;
@@ -409,6 +410,9 @@
         doNothing().when(mConnectivityManager)
                 .registerNetworkCallback(any(), mNetworkCallbackCaptor.capture());
 
+        // Create the expected carrier config
+        mCarrierConfig.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
+
         // Prepare NPMS.
         mService.systemReady(mService.networkScoreAndNetworkManagementServiceReady());
 
@@ -1086,6 +1090,25 @@
                     isA(Notification.class), eq(UserHandle.ALL));
         }
 
+        // Push over warning, but with a config that isn't from an identified carrier
+        {
+            history.clear();
+            history.recordData(start, end,
+                    new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1799), 0L, 0L, 0L, 0));
+
+            reset(mTelephonyManager, mNetworkManager, mNotifManager);
+            expectMobileDefaults();
+            expectDefaultCarrierConfig();
+
+            mService.updateNetworks();
+
+            verify(mTelephonyManager, atLeastOnce()).setPolicyDataEnabled(true, TEST_SUB_ID);
+            verify(mNetworkManager, atLeastOnce()).setInterfaceQuota(TEST_IFACE,
+                    DataUnit.MEGABYTES.toBytes(1800 - 1799));
+            // Since this isn't from the identified carrier, there should be no notifications
+            verify(mNotifManager, never()).notifyAsUser(any(), anyInt(), any(), any());
+        }
+
         // Push over limit
         {
             history.clear();
@@ -1812,7 +1835,7 @@
 
     private void expectNetworkState(boolean roaming) throws Exception {
         when(mCarrierConfigManager.getConfigForSubId(eq(TEST_SUB_ID)))
-                .thenReturn(CarrierConfigManager.getDefaultConfig());
+                .thenReturn(mCarrierConfig);
         when(mConnManager.getAllNetworkState()).thenReturn(new NetworkState[] {
                 new NetworkState(buildNetworkInfo(),
                         buildLinkProperties(TEST_IFACE),
@@ -1821,10 +1844,16 @@
         });
     }
 
+    private void expectDefaultCarrierConfig() throws Exception {
+        when(mCarrierConfigManager.getConfigForSubId(eq(TEST_SUB_ID)))
+                .thenReturn(CarrierConfigManager.getDefaultConfig());
+    }
+
     private void expectMobileDefaults() throws Exception {
         when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(
                 new int[] { TEST_SUB_ID });
         when(mTelephonyManager.getSubscriberId(TEST_SUB_ID)).thenReturn(TEST_IMSI);
+        doNothing().when(mTelephonyManager).setPolicyDataEnabled(anyBoolean(), anyInt());
         expectNetworkState(false /* roaming */);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/AnimatingAppWindowTokenRegistryTest.java b/services/tests/wmtests/src/com/android/server/wm/AnimatingAppWindowTokenRegistryTest.java
index 380f7c6..5cd649f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AnimatingAppWindowTokenRegistryTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AnimatingAppWindowTokenRegistryTest.java
@@ -27,6 +27,7 @@
 
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
@@ -80,6 +81,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 131005232)
     public void testContainerRemoved() {
         final AppWindowToken window1 = createAppWindowToken(mDisplayContent,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
index 83c0af9..777e4f4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
@@ -38,6 +38,7 @@
 import android.view.RemoteAnimationDefinition;
 import android.view.RemoteAnimationTarget;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
@@ -96,6 +97,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 131005232)
     public void testModeChangeRemoteAnimatorNoSnapshot() {
         // setup currently defaults to no snapshot.
         setUpOnDisplay(mDisplayContent);
@@ -113,6 +115,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 131005232)
     public void testCancelPendingChangeOnRemove() {
         // setup currently defaults to no snapshot.
         setUpOnDisplay(mDisplayContent);
@@ -132,6 +135,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 131005232)
     public void testNoChangeWhenMoveDisplay() {
         mDisplayContent.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         final DisplayContent dc1 = createNewDisplay(Display.STATE_ON);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index b1ffbbd..2d1dd39 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -31,6 +31,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.view.WindowManager;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
@@ -52,6 +53,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 131005232)
     public void testTranslucentOpen() {
         synchronized (mWm.mGlobalLock) {
             final AppWindowToken behind = createAppWindowToken(mDisplayContent,
@@ -69,6 +71,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 131005232)
     public void testTranslucentClose() {
         synchronized (mWm.mGlobalLock) {
             final AppWindowToken behind = createAppWindowToken(mDisplayContent,
@@ -84,6 +87,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 131005232)
     public void testChangeIsNotOverwritten() {
         synchronized (mWm.mGlobalLock) {
             final AppWindowToken behind = createAppWindowToken(mDisplayContent,
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 6e09167..9cdea9a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -39,6 +39,7 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 /**
@@ -78,6 +79,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 131005232)
     public void testForceOverride() {
         mWm.prepareAppTransition(TRANSIT_KEYGUARD_UNOCCLUDE, false /* alwaysKeepCurrent */);
         mDc.prepareAppTransition(TRANSIT_ACTIVITY_OPEN,
@@ -93,6 +95,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 131005232)
     public void testKeepKeyguard_withCrashing() {
         mWm.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, false /* alwaysKeepCurrent */);
         mWm.prepareAppTransition(TRANSIT_CRASHING_ACTIVITY_CLOSE, false /* alwaysKeepCurrent */);
@@ -100,6 +103,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 131005232)
     public void testAppTransitionStateForMultiDisplay() {
         // Create 2 displays & presume both display the state is ON for ready to display & animate.
         final DisplayContent dc1 = createNewDisplay(Display.STATE_ON);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowThumbnailTest.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowThumbnailTest.java
index fa0c384..b8f8e21 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowThumbnailTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowThumbnailTest.java
@@ -28,6 +28,7 @@
 import android.view.Surface;
 import android.view.SurfaceControl;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
@@ -53,6 +54,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 131005232)
     public void testDestroy_nullsSurface() {
         final AppWindowThumbnail t = buildThumbnail();
         assertNotNull(t.getSurfaceControl());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 196cc21..0110e94 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -39,6 +39,7 @@
 import android.view.SurfaceSession;
 import android.view.View;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 import com.android.server.LocalServices;
@@ -146,16 +147,19 @@
     }
 
     @Test
+    @FlakyTest(bugId = 131005232)
     public void testDragFlow() {
         dragFlow(0, ClipData.newPlainText("label", "Test"), 0, 0);
     }
 
     @Test
+    @FlakyTest(bugId = 131005232)
     public void testPerformDrag_NullDataWithGrantUri() {
         dragFlow(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, null, 0, 0);
     }
 
     @Test
+    @FlakyTest(bugId = 131005232)
     public void testPerformDrag_NullDataToOtherUser() {
         final WindowState otherUsersWindow =
                 createDropTargetWindow("Other user's window", 1 * UserHandle.PER_USER_RANGE);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index a0546d7..86ee75e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -31,6 +31,7 @@
 import android.view.InsetsSource;
 import android.view.InsetsState;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 2d906d1..9fce78b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -58,6 +58,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 131005232)
     public void testStripForDispatch_notOwn() {
         final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
         final WindowState app = createWindow(null, TYPE_APPLICATION, "parentWindow");
diff --git a/services/tests/wmtests/src/com/android/server/wm/PendingRemoteAnimationRegistryTest.java b/services/tests/wmtests/src/com/android/server/wm/PendingRemoteAnimationRegistryTest.java
index b769fce..103c3ab 100644
--- a/services/tests/wmtests/src/com/android/server/wm/PendingRemoteAnimationRegistryTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/PendingRemoteAnimationRegistryTest.java
@@ -24,6 +24,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.view.RemoteAnimationAdapter;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 import com.android.server.testutils.OffsettableClock;
@@ -73,6 +74,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 131005232)
     public void testTimeout() {
         mRegistry.addPendingAnimation("com.android.test", mAdapter);
         mClock.fastForward(5000);
diff --git a/services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java b/services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java
index 4673992..027f903 100644
--- a/services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java
@@ -110,6 +110,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 131005232)
     public void testProcessOneItem_Flush() throws Exception {
         mFactory.setExpectedProcessedItemNumber(1);
         mListener.setExpectedOnPreProcessItemCallbackTimes(1);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
index 6d27e6d..bac1ecd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -31,7 +31,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.server.wm.ActivityDisplay.POSITION_TOP;
@@ -56,7 +55,6 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.util.Pair;
@@ -539,71 +537,6 @@
     }
 
     /**
-     * Tests that the default secondary home activity is always picked when it is in forced by
-     * config_useSystemProvidedLauncherForSecondary.
-     */
-    @Test
-    public void testResolveSecondaryHomeActivityForced() {
-        Resources resources = mContext.getResources();
-        spyOn(resources);
-        final String defaultSecondaryHome =
-                "com.android.test/com.android.test.TestDefaultSecondaryHome";
-        final ComponentName secondaryComp = ComponentName.unflattenFromString(defaultSecondaryHome);
-        doReturn(defaultSecondaryHome).when(resources).getString(
-                com.android.internal.R.string.config_secondaryHomeComponent);
-        doReturn(true).when(resources).getBoolean(
-                com.android.internal.R.bool.config_useSystemProvidedLauncherForSecondary);
-
-        final Intent secondaryHomeIntent = mService.getSecondaryHomeIntent(null);
-        assertEquals(secondaryComp, secondaryHomeIntent.getComponent());
-
-        final ActivityInfo aInfoSecondary = new ActivityInfo();
-        aInfoSecondary.name = secondaryComp.getClassName();
-        aInfoSecondary.applicationInfo = new ApplicationInfo();
-        aInfoSecondary.applicationInfo.packageName = secondaryComp.getPackageName();
-        doReturn(aInfoSecondary).when(mRootActivityContainer).resolveHomeActivity(anyInt(),
-                refEq(secondaryHomeIntent));
-
-        final Intent homeIntent = mService.getHomeIntent();
-        final ActivityInfo aInfoDefault = new ActivityInfo();
-        aInfoDefault.name = "fakeHomeActivity";
-        aInfoDefault.applicationInfo = new ApplicationInfo();
-        aInfoDefault.applicationInfo.packageName = "fakeHomePackage";
-        doReturn(aInfoDefault).when(mRootActivityContainer).resolveHomeActivity(anyInt(),
-                refEq(homeIntent));
-
-        // Let resolveActivities call to validate both main launcher and second launcher so that
-        // resolveActivities call does not work as enabler for secondary.
-        final List<ResolveInfo> resolutions1 = new ArrayList<>();
-        final ResolveInfo resolveInfo1 = new ResolveInfo();
-        resolveInfo1.activityInfo = new ActivityInfo();
-        resolveInfo1.activityInfo.name = aInfoDefault.name;
-        resolveInfo1.activityInfo.applicationInfo = aInfoDefault.applicationInfo;
-        resolutions1.add(resolveInfo1);
-        doReturn(resolutions1).when(mRootActivityContainer).resolveActivities(anyInt(),
-                refEq(homeIntent));
-        final List<ResolveInfo> resolutions2 = new ArrayList<>();
-        final ResolveInfo resolveInfo2 = new ResolveInfo();
-        resolveInfo2.activityInfo = new ActivityInfo();
-        resolveInfo2.activityInfo.name = aInfoSecondary.name;
-        resolveInfo2.activityInfo.applicationInfo = aInfoSecondary.applicationInfo;
-        resolutions2.add(resolveInfo2);
-        doReturn(resolutions2).when(mRootActivityContainer).resolveActivities(anyInt(),
-                refEq(secondaryHomeIntent));
-
-        doReturn(true).when(mRootActivityContainer).canStartHomeOnDisplay(
-                any(), anyInt(), anyBoolean());
-
-        final Pair<ActivityInfo, Intent> resolvedInfo = mRootActivityContainer
-                .resolveSecondaryHomeActivity(0 /* userId */, 1 /* displayId */);
-
-        assertEquals(secondaryComp.getClassName(), resolvedInfo.first.name);
-        assertEquals(secondaryComp.getPackageName(),
-                resolvedInfo.first.applicationInfo.packageName);
-        assertEquals(aInfoSecondary.name, resolvedInfo.first.name);
-    }
-
-    /**
      * Tests that secondary home should be selected if default home not support secondary displays
      * or there is no matched activity in the same package as selected default home.
      */
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskPersisterTest.java
index 9dfeadf..9fc1602 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPersisterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPersisterTest.java
@@ -29,6 +29,8 @@
 import android.platform.test.annotations.Presubmit;
 import android.util.SparseBooleanArray;
 
+import androidx.test.filters.FlakyTest;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -72,6 +74,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 131005232)
     public void testTaskIdsPersistence() {
         SparseBooleanArray taskIdsOnFile = new SparseBooleanArray();
         for (int i = 0; i < 100; i++) {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 6387334..69500d7 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3313,7 +3313,7 @@
                 });
         sDefaults.putStringArray(KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY, new String[0]);
         sDefaults.putBoolean(KEY_USE_USIM_BOOL, false);
-        sDefaults.putBoolean(KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL, true);
+        sDefaults.putBoolean(KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL, false);
         sDefaults.putBoolean(KEY_AUTO_CANCEL_CS_REJECT_NOTIFICATION, false);
         sDefaults.putString(KEY_SMART_FORWARDING_CONFIG_COMPONENT_NAME_STRING, "");
         sDefaults.putBoolean(KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN,
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index 373c5d2..3ce28a4 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -38,6 +38,8 @@
 import java.util.Map;
 import java.util.concurrent.Executor;
 
+import dalvik.system.VMRuntime;
+
 /**
  * A listener class for monitoring changes in specific telephony states
  * on the device, including service state, signal strength, message
@@ -400,8 +402,12 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public PhoneStateListener(Integer subId) {
         this(subId, Looper.myLooper());
+        if (subId != null && VMRuntime.getRuntime().getTargetSdkVersion()
+                >= Build.VERSION_CODES.Q) {
+            throw new IllegalArgumentException("PhoneStateListener with subId: "
+                    + subId + " is not supported, use default constructor");
+        }
     }
-
     /**
      * Create a PhoneStateListener for the Phone using the specified subscription
      * and non-null Looper.
@@ -410,6 +416,11 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public PhoneStateListener(Integer subId, Looper looper) {
         this(subId, new HandlerExecutor(new Handler(looper)));
+        if (subId != null && VMRuntime.getRuntime().getTargetSdkVersion()
+                >= Build.VERSION_CODES.Q) {
+            throw new IllegalArgumentException("PhoneStateListener with subId: "
+                    + subId + " is not supported, use default constructor");
+        }
     }
 
     /**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f0cad6a..903e533 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -108,6 +108,8 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import dalvik.system.VMRuntime;
+
 /**
  * Provides access to information about the telephony services on
  * the device. Applications can use the methods in this class to
@@ -4863,18 +4865,22 @@
      * Registers a listener object to receive notification of changes
      * in specified telephony states.
      * <p>
-     * To register a listener, pass a {@link PhoneStateListener}
-     * and specify at least one telephony state of interest in
-     * the events argument.
+     * To register a listener, pass a {@link PhoneStateListener} and specify at least one telephony
+     * state of interest in the events argument.
      *
-     * At registration, and when a specified telephony state
-     * changes, the telephony manager invokes the appropriate
-     * callback method on the listener object and passes the
-     * current (updated) values.
+     * At registration, and when a specified telephony state changes, the telephony manager invokes
+     * the appropriate callback method on the listener object and passes the current (updated)
+     * values.
      * <p>
-     * To unregister a listener, pass the listener object and set the
-     * events argument to
+     * To un-register a listener, pass the listener object and set the events argument to
      * {@link PhoneStateListener#LISTEN_NONE LISTEN_NONE} (0).
+     *
+     * If this TelephonyManager object has been created with {@link #createForSubscriptionId},
+     * applies to the given subId. Otherwise, applies to
+     * {@link SubscriptionManager#getDefaultSubscriptionId()}. To listen events for multiple subIds,
+     * pass a separate listener object to each TelephonyManager object created with
+     * {@link #createForSubscriptionId}.
+     *
      * Note: if you call this method while in the middle of a binder transaction, you <b>must</b>
      * call {@link android.os.Binder#clearCallingIdentity()} before calling this method. A
      * {@link SecurityException} will be thrown otherwise.
@@ -4889,17 +4895,26 @@
         if (mContext == null) return;
         try {
             boolean notifyNow = (getITelephony() != null);
-            // If the listener has not explicitly set the subId (for example, created with the
-            // default constructor), replace the subId so it will listen to the account the
-            // telephony manager is created with.
-            if (listener.mSubId == null) {
-                listener.mSubId = mSubId;
-            }
-
             ITelephonyRegistry registry = getTelephonyRegistry();
             if (registry != null) {
-                registry.listenForSubscriber(listener.mSubId, getOpPackageName(),
+                int subId;
+                // subId from phonestatelistner is deprecated Q on forward, use the subId from
+                // TelephonyManager instance.
+                if (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q
+                        || listener.mSubId == null) {
+                    subId = mSubId;
+                } else {
+                    subId = listener.mSubId;
+                }
+
+                registry.listenForSubscriber(subId, getOpPackageName(),
                         listener.callback, events, notifyNow);
+                // TODO: remove this once we remove PhoneStateListener constructor with subId.
+                if (events == PhoneStateListener.LISTEN_NONE) {
+                    listener.mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+                } else {
+                    listener.mSubId = subId;
+                }
             } else {
                 Rlog.w(TAG, "telephony registry not ready.");
             }