Merge "Remove burn in offset on lock screen." into qt-dev
diff --git a/Android.bp b/Android.bp
index 0fcc0d5..abf95a8 100644
--- a/Android.bp
+++ b/Android.bp
@@ -687,6 +687,7 @@
         "core/java/com/android/server/DropboxLogTags.logtags",
         "core/java/org/chromium/arc/EventLogTags.logtags",
 
+        ":apex-properties",
         ":platform-properties",
 
         ":framework-statslog-gen",
@@ -1834,4 +1835,4 @@
     name: "framework-aidl-mappings",
     srcs: [":framework-defaults"],
     output: "framework-aidl-mappings.txt"
-}
\ No newline at end of file
+}
diff --git a/api/current.txt b/api/current.txt
index ec7df66..8b24826 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -38820,8 +38820,7 @@
     field @Deprecated public static final String LOCATION_MODE = "location_mode";
     field @Deprecated public static final int LOCATION_MODE_BATTERY_SAVING = 2; // 0x2
     field @Deprecated public static final int LOCATION_MODE_HIGH_ACCURACY = 3; // 0x3
-    field @Deprecated public static final int LOCATION_MODE_OFF = 0; // 0x0
-    field @Deprecated public static final int LOCATION_MODE_ON = 3; // 0x3
+    field public static final int LOCATION_MODE_OFF = 0; // 0x0
     field @Deprecated public static final int LOCATION_MODE_SENSORS_ONLY = 1; // 0x1
     field @Deprecated public static final String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed";
     field @Deprecated public static final String LOCK_PATTERN_ENABLED = "lock_pattern_autolock";
diff --git a/api/system-current.txt b/api/system-current.txt
index 2ce1ee1..e479c48 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6046,6 +6046,7 @@
     field public static final String LAST_SETUP_SHOWN = "last_setup_shown";
     field public static final String LOCATION_ACCESS_CHECK_DELAY_MILLIS = "location_access_check_delay_millis";
     field public static final String LOCATION_ACCESS_CHECK_INTERVAL_MILLIS = "location_access_check_interval_millis";
+    field public static final int LOCATION_MODE_ON = 3; // 0x3
     field public static final String LOCATION_PERMISSIONS_UPGRADE_TO_Q_MODE = "location_permissions_upgrade_to_q_mode";
     field public static final String LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS = "lock_screen_allow_private_notifications";
     field public static final String LOCK_SCREEN_SHOW_NOTIFICATIONS = "lock_screen_show_notifications";
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 1433252..623a1f2 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -28,6 +28,7 @@
 
 #include <android-base/file.h>
 #include <android-base/stringprintf.h>
+#include <android-base/strings.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/PermissionController.h>
@@ -394,6 +395,10 @@
             return cmd_log_app_breadcrumb(out, args);
         }
 
+        if (!args[0].compare(String8("log-binary-push"))) {
+            return cmd_log_binary_push(out, args);
+        }
+
         if (!args[0].compare(String8("clear-puller-cache"))) {
             return cmd_clear_puller_cache(out);
         }
@@ -461,6 +466,21 @@
     dprintf(out, "  STATE         Integer in [0, 3], as per atoms.proto.\n");
     dprintf(out, "\n");
     dprintf(out, "\n");
+    dprintf(out,
+            "usage: adb shell cmd stats log-binary-push NAME VERSION STAGING ROLLBACK_ENABLED "
+            "LOW_LATENCY STATE EXPERIMENT_IDS\n");
+    dprintf(out, "  Log a binary push state changed event.\n");
+    dprintf(out, "  NAME                The train name.\n");
+    dprintf(out, "  VERSION             The train version code.\n");
+    dprintf(out, "  STAGING             If this train requires a restart.\n");
+    dprintf(out, "  ROLLBACK_ENABLED    If rollback should be enabled for this install.\n");
+    dprintf(out, "  LOW_LATENCY         If the train requires low latency monitoring.\n");
+    dprintf(out, "  STATE               The status of the train push.\n");
+    dprintf(out, "                      Integer value of the enum in atoms.proto.\n");
+    dprintf(out, "  EXPERIMENT_IDS      Comma separated list of experiment ids.\n");
+    dprintf(out, "                      Leave blank for none.\n");
+    dprintf(out, "\n");
+    dprintf(out, "\n");
     dprintf(out, "usage: adb shell cmd stats config remove [UID] [NAME]\n");
     dprintf(out, "usage: adb shell cmd stats config update [UID] NAME\n");
     dprintf(out, "\n");
@@ -506,7 +526,6 @@
     dprintf(out, "  --configs     Send the list of configs in the name list instead of\n");
     dprintf(out, "                the currently active configs\n");
     dprintf(out, "  NAME LIST     List of configuration names to be included in the broadcast.\n");
-
     dprintf(out, "\n");
     dprintf(out, "\n");
     dprintf(out, "usage: adb shell cmd stats print-stats\n");
@@ -821,6 +840,39 @@
     return NO_ERROR;
 }
 
+status_t StatsService::cmd_log_binary_push(int out, const Vector<String8>& args) {
+    // Security checks are done in the sendBinaryPushStateChanged atom.
+    const int argCount = args.size();
+    if (argCount != 7 && argCount != 8) {
+        dprintf(out, "Incorrect number of argument supplied\n");
+        return UNKNOWN_ERROR;
+    }
+    android::String16 trainName = android::String16(args[1].c_str());
+    int64_t trainVersion = strtoll(args[2].c_str(), nullptr, 10);
+    int options = 0;
+    if (args[3] == "1") {
+        options = options | IStatsManager::FLAG_REQUIRE_STAGING;
+    }
+    if (args[4] == "1") {
+        options = options | IStatsManager::FLAG_ROLLBACK_ENABLED;
+    }
+    if (args[5] == "1") {
+        options = options | IStatsManager::FLAG_REQUIRE_LOW_LATENCY_MONITOR;
+    }
+    int32_t state = atoi(args[6].c_str());
+    vector<int64_t> experimentIds;
+    if (argCount == 8) {
+        vector<string> experimentIdsString = android::base::Split(string(args[7].c_str()), ",");
+        for (string experimentIdString : experimentIdsString) {
+            int64_t experimentId = strtoll(experimentIdString.c_str(), nullptr, 10);
+            experimentIds.push_back(experimentId);
+        }
+    }
+    dprintf(out, "Logging BinaryPushStateChanged\n");
+    sendBinaryPushStateChangedAtom(trainName, trainVersion, options, state, experimentIds);
+    return NO_ERROR;
+}
+
 status_t StatsService::cmd_print_pulled_metrics(int out, const Vector<String8>& args) {
     int s = atoi(args[1].c_str());
     vector<shared_ptr<LogEvent> > stats;
@@ -986,7 +1038,7 @@
     ENFORCE_UID(AID_SYSTEM);
     VLOG("StatsService::informDeviceShutdown");
     mProcessor->WriteDataToDisk(DEVICE_SHUTDOWN, FAST);
-    mProcessor->WriteMetricsActivationToDisk(getElapsedRealtimeNs());
+    mProcessor->SaveActiveConfigsToDisk(getElapsedRealtimeNs());
     return Status::ok();
 }
 
@@ -1021,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());
     }
 }
 
@@ -1202,56 +1254,90 @@
     return Status::ok();
 }
 
-Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& trainName,
-                                                    int64_t trainVersionCode, int options,
-                                                    int32_t state,
+Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& trainNameIn,
+                                                    const int64_t trainVersionCodeIn,
+                                                    const int options,
+                                                    const int32_t state,
                                                     const std::vector<int64_t>& experimentIdsIn) {
+    // Note: We skip the usage stats op check here since we do not have a package name.
+    // This is ok since we are overloading the usage_stats permission.
+    // This method only sends data, it does not receive it.
+    pid_t pid = IPCThreadState::self()->getCallingPid();
     uid_t uid = IPCThreadState::self()->getCallingUid();
-    // For testing
-    if (uid == AID_ROOT || uid == AID_SYSTEM || uid == AID_SHELL) {
-        return ok();
+        // Root, system, and shell always have access
+    if (uid != AID_ROOT && uid != AID_SYSTEM && uid != AID_SHELL) {
+        // Caller must be granted these permissions
+        if (!checkCallingPermission(String16(kPermissionDump))) {
+            return exception(binder::Status::EX_SECURITY,
+                             StringPrintf("UID %d / PID %d lacks permission %s", uid, pid,
+                                          kPermissionDump));
+        }
+        if (!checkCallingPermission(String16(kPermissionUsage))) {
+            return exception(binder::Status::EX_SECURITY,
+                             StringPrintf("UID %d / PID %d lacks permission %s", uid, pid,
+                                          kPermissionUsage));
+        }
     }
 
-    // Caller must be granted these permissions
-    if (!checkCallingPermission(String16(kPermissionDump))) {
-        return exception(binder::Status::EX_SECURITY,
-                         StringPrintf("UID %d lacks permission %s", uid, kPermissionDump));
-    }
-    if (!checkCallingPermission(String16(kPermissionUsage))) {
-        return exception(binder::Status::EX_SECURITY,
-                         StringPrintf("UID %d lacks permission %s", uid, kPermissionUsage));
-    }
-    // TODO: add verifier permission
-
     bool readTrainInfoSuccess = false;
-    InstallTrainInfo trainInfo;
-    if (trainVersionCode == -1 || experimentIdsIn.empty() || trainName.size() == 0) {
-        readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfo);
-    }
+    InstallTrainInfo trainInfoOnDisk;
+    readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfoOnDisk);
 
-    if (trainVersionCode == -1 && readTrainInfoSuccess) {
-        trainVersionCode = trainInfo.trainVersionCode;
+    bool resetExperimentIds = false;
+    int64_t trainVersionCode = trainVersionCodeIn;
+    std::string trainNameUtf8 = std::string(String8(trainNameIn).string());
+    if (readTrainInfoSuccess) {
+        // Keep the old train version if we received an empty version.
+        if (trainVersionCodeIn == -1) {
+            trainVersionCode = trainInfoOnDisk.trainVersionCode;
+        } else if (trainVersionCodeIn != trainInfoOnDisk.trainVersionCode) {
+        // Reset experiment ids if we receive a new non-empty train version.
+            resetExperimentIds = true;
+        }
+
+        // Keep the old train name if we received an empty train name.
+        if (trainNameUtf8.size() == 0) {
+            trainNameUtf8 = trainInfoOnDisk.trainName;
+        } else if (trainNameUtf8 != trainInfoOnDisk.trainName) {
+            // Reset experiment ids if we received a new valid train name.
+            resetExperimentIds = true;
+        }
+
+        // Reset if we received a different experiment id.
+        if (!experimentIdsIn.empty() &&
+                (trainInfoOnDisk.experimentIds.empty() ||
+                 experimentIdsIn[0] != trainInfoOnDisk.experimentIds[0])) {
+            resetExperimentIds = true;
+        }
     }
 
     // Find the right experiment IDs
     std::vector<int64_t> experimentIds;
-    if (readTrainInfoSuccess && experimentIdsIn.empty()) {
-        experimentIds = trainInfo.experimentIds;
-    } else {
+    if (resetExperimentIds || !readTrainInfoSuccess) {
         experimentIds = experimentIdsIn;
+    } else {
+        experimentIds = trainInfoOnDisk.experimentIds;
+    }
+
+    if (!experimentIds.empty()) {
+        int64_t firstId = experimentIds[0];
+        switch (state) {
+            case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALL_SUCCESS:
+                experimentIds.push_back(firstId + 1);
+                break;
+            case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_INITIATED:
+                experimentIds.push_back(firstId + 2);
+                break;
+            case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_SUCCESS:
+                experimentIds.push_back(firstId + 3);
+                break;
+        }
     }
 
     // Flatten the experiment IDs to proto
     vector<uint8_t> experimentIdsProtoBuffer;
     writeExperimentIdsToProto(experimentIds, &experimentIdsProtoBuffer);
-
-    // Find the right train name
-    std::string trainNameUtf8;
-    if (readTrainInfoSuccess && trainName.size() == 0) {
-        trainNameUtf8 = trainInfo.trainName;
-    } else {
-        trainNameUtf8 = std::string(String8(trainName).string());
-    }
+    StorageManager::writeTrainInfo(trainVersionCode, trainNameUtf8, state, experimentIds);
 
     userid_t userId = multiuser_get_user_id(uid);
     bool requiresStaging = options & IStatsManager::FLAG_REQUIRE_STAGING;
@@ -1260,7 +1346,6 @@
     LogEvent event(trainNameUtf8, trainVersionCode, requiresStaging, rollbackEnabled,
                    requiresLowLatencyMonitor, state, experimentIdsProtoBuffer, userId);
     mProcessor->OnLogEvent(&event);
-    StorageManager::writeTrainInfo(trainVersionCode, trainNameUtf8, state, experimentIds);
     return Status::ok();
 }
 
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 929d260..936f7db 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -189,8 +189,11 @@
      * Binder call to log BinaryPushStateChanged atom.
      */
     virtual Status sendBinaryPushStateChangedAtom(
-            const android::String16& trainName, int64_t trainVersionCode, int options,
-            int32_t state, const std::vector<int64_t>& experimentIds) override;
+            const android::String16& trainNameIn,
+            const int64_t trainVersionCodeIn,
+            const int options,
+            const int32_t state,
+            const std::vector<int64_t>& experimentIdsIn) override;
 
     /**
      * Binder call to get registered experiment IDs.
@@ -328,6 +331,11 @@
     status_t cmd_log_app_breadcrumb(int outFd, const Vector<String8>& args);
 
     /**
+     * Write an BinaryPushStateChanged event, as if calling StatsLog.logBinaryPushStateChanged().
+     */
+    status_t cmd_log_binary_push(int outFd, const Vector<String8>& args);
+
+    /**
      * Print contents of a pulled metrics source.
      */
     status_t cmd_print_pulled_metrics(int outFd, const Vector<String8>& args);
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/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index ca874b5..0ade531 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -678,7 +678,8 @@
     writeFieldValueTreeToStream(mTagId, getValues(), &protoOutput);
 }
 
-void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, std::vector<uint8_t>* protoOut) {
+void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds,
+                               std::vector<uint8_t>* protoOut) {
     ProtoOutputStream proto;
     for (const auto& expId : experimentIds) {
         proto.write(FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED | FIELD_ID_EXPERIMENT_ID,
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/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 7349f0c..8596af10 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -40,6 +40,7 @@
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.Slog;
@@ -225,6 +226,7 @@
                 mAuthenticationCallback = callback;
                 mCryptoObject = crypto;
                 long sessionId = crypto != null ? crypto.getOpId() : 0;
+                Trace.beginSection("FaceManager#authenticate");
                 mService.authenticate(mToken, sessionId, userId, mServiceReceiver,
                         flags, mContext.getOpPackageName());
             } catch (RemoteException e) {
@@ -236,6 +238,8 @@
                             getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
                                     0 /* vendorCode */));
                 }
+            } finally {
+                Trace.endSection();
             }
         }
     }
@@ -276,6 +280,7 @@
         if (mService != null) {
             try {
                 mEnrollmentCallback = callback;
+                Trace.beginSection("FaceManager#enroll");
                 mService.enroll(mToken, token, mServiceReceiver,
                         mContext.getOpPackageName(), disabledFeatures);
             } catch (RemoteException e) {
@@ -287,6 +292,8 @@
                             getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
                                 0 /* vendorCode */));
                 }
+            } finally {
+                Trace.endSection();
             }
         }
     }
@@ -965,6 +972,7 @@
 
         @Override
         public void handleMessage(android.os.Message msg) {
+            Trace.beginSection("FaceManager#handleMessage: " + Integer.toString(msg.what));
             switch (msg.what) {
                 case MSG_ENROLL_RESULT:
                     sendEnrollResult((Face) msg.obj, msg.arg1 /* remaining */);
@@ -1000,6 +1008,7 @@
                 default:
                     Log.w(TAG, "Unknown message: " + msg.what);
             }
+            Trace.endSection();
         }
     }
 
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 9215de1..774d4ae 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -159,6 +159,14 @@
     public static final String NAMESPACE_INTELLIGENCE_ATTENTION = "intelligence_attention";
 
     /**
+     * Definitions for properties related to Content Suggestions.
+     *
+     * @hide
+     */
+    public static final String NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS =
+            "intelligence_content_suggestions";
+
+    /**
      * Namespace for all media native related features.
      *
      * @hide
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4da0d2d..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
 
     /**
@@ -6053,9 +6105,8 @@
                 "unknown_sources_default_reversed";
 
         /**
-         * Comma-separated list of location providers that are accessible. Do not rely on
-         * this value being present or correct, or on ContentObserver notifications on the
-         * corresponding Uri.
+         * Comma-separated list of location providers that are enabled. Do not rely on this value
+         * being present or correct, or on ContentObserver notifications on the corresponding Uri.
          *
          * @deprecated The preferred methods for checking provider status and listening for changes
          * are via {@link LocationManager#isProviderEnabled(String)} and
@@ -6098,17 +6149,14 @@
 
         /**
          * Location mode is off.
-         *
-         * @deprecated See {@link #LOCATION_MODE}.
          */
-        @Deprecated
         public static final int LOCATION_MODE_OFF = 0;
 
         /**
          * This mode no longer has any distinct meaning, but is interpreted as the location mode is
          * on.
          *
-         * @deprecated See {@link #LOCATION_MODE_ON}.
+         * @deprecated See {@link #LOCATION_MODE}.
          */
         @Deprecated
         public static final int LOCATION_MODE_SENSORS_ONLY = 1;
@@ -6117,7 +6165,7 @@
          * This mode no longer has any distinct meaning, but is interpreted as the location mode is
          * on.
          *
-         * @deprecated See {@link #LOCATION_MODE_ON}.
+         * @deprecated See {@link #LOCATION_MODE}.
          */
         @Deprecated
         public static final int LOCATION_MODE_BATTERY_SAVING = 2;
@@ -6126,7 +6174,7 @@
          * This mode no longer has any distinct meaning, but is interpreted as the location mode is
          * on.
          *
-         * @deprecated See {@link #LOCATION_MODE_ON}.
+         * @deprecated See {@link #LOCATION_MODE}.
          */
         @Deprecated
         public static final int LOCATION_MODE_HIGH_ACCURACY = 3;
@@ -6134,9 +6182,9 @@
         /**
          * Location mode is on.
          *
-         * @deprecated See {@link #LOCATION_MODE}.
+         * @hide
          */
-        @Deprecated
+        @SystemApi
         public static final int LOCATION_MODE_ON = LOCATION_MODE_HIGH_ACCURACY;
 
         /**
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/os/RoSystemProperties.java b/core/java/com/android/internal/os/RoSystemProperties.java
index b0855f4..8182d60 100644
--- a/core/java/com/android/internal/os/RoSystemProperties.java
+++ b/core/java/com/android/internal/os/RoSystemProperties.java
@@ -60,7 +60,7 @@
     public static final boolean FW_SYSTEM_USER_SPLIT =
             SystemProperties.getBoolean("ro.fw.system_user_split", false);
     public static final boolean MULTIUSER_HEADLESS_SYSTEM_USER =
-            SystemProperties.getBoolean("ro.fw.multiuser.headless_system_user", false);
+            SystemProperties.getBoolean("ro.fw.mu.headless_system_user", false);
 
     // ------ ro.crypto.* -------- //
     public static final CryptoProperties.state_values CRYPTO_STATE =
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/NetworkPermissionConfig/AndroidManifest.xml b/packages/NetworkPermissionConfig/AndroidManifest.xml
index 34f987c..496fa4d 100644
--- a/packages/NetworkPermissionConfig/AndroidManifest.xml
+++ b/packages/NetworkPermissionConfig/AndroidManifest.xml
@@ -19,7 +19,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.networkstack.permissionconfig"
     android:sharedUserId="android.uid.networkstack"
-    android:versionCode="11"
+    android:versionCode="210000000"
     android:versionName="Q-initial">
     <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
     <!--
diff --git a/packages/NetworkStack/src/android/net/apf/ApfFilter.java b/packages/NetworkStack/src/android/net/apf/ApfFilter.java
index 663e2f1..359c859 100644
--- a/packages/NetworkStack/src/android/net/apf/ApfFilter.java
+++ b/packages/NetworkStack/src/android/net/apf/ApfFilter.java
@@ -39,6 +39,7 @@
 import android.content.IntentFilter;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.NattKeepalivePacketDataParcelable;
 import android.net.TcpKeepalivePacketDataParcelable;
 import android.net.apf.ApfGenerator.IllegalInstructionException;
 import android.net.apf.ApfGenerator.Register;
@@ -1691,13 +1692,13 @@
     }
 
     /**
-     * Add keepalive ack packet filter.
+     * Add TCP keepalive ack packet filter.
      * This will add a filter to drop acks to the keepalive packet passed as an argument.
      *
      * @param slot The index used to access the filter.
      * @param sentKeepalivePacket The attributes of the sent keepalive packet.
      */
-    public synchronized void addKeepalivePacketFilter(final int slot,
+    public synchronized void addTcpKeepalivePacketFilter(final int slot,
             final TcpKeepalivePacketDataParcelable sentKeepalivePacket) {
         log("Adding keepalive ack(" + slot + ")");
         if (null != mKeepaliveAcks.get(slot)) {
@@ -1711,6 +1712,18 @@
     }
 
     /**
+     * Add NATT keepalive packet filter.
+     * This will add a filter to drop NATT keepalive packet which is passed as an argument.
+     *
+     * @param slot The index used to access the filter.
+     * @param sentKeepalivePacket The attributes of the sent keepalive packet.
+     */
+    public synchronized void addNattKeepalivePacketFilter(final int slot,
+            final NattKeepalivePacketDataParcelable sentKeepalivePacket) {
+        Log.e(TAG, "APF add NATT keepalive filter is not implemented");
+    }
+
+    /**
      * Remove keepalive packet filter.
      *
      * @param slot The index used to access the filter.
diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java
index 96e09fa..dc74c04 100644
--- a/packages/NetworkStack/src/android/net/ip/IpClient.java
+++ b/packages/NetworkStack/src/android/net/ip/IpClient.java
@@ -29,6 +29,7 @@
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.NattKeepalivePacketDataParcelable;
 import android.net.NetworkStackIpMemoryStore;
 import android.net.ProvisioningConfigurationParcelable;
 import android.net.ProxyInfo;
@@ -371,6 +372,10 @@
     private boolean mMulticastFiltering;
     private long mStartTimeMillis;
 
+    /* This must match the definition in KeepaliveTracker.KeepaliveInfo */
+    private static final int TYPE_NATT = 1;
+    private static final int TYPE_TCP = 2;
+
     /**
      * Reading the snapshot is an asynchronous operation initiated by invoking
      * Callback.startReadPacketFilter() and completed when the WiFi Service responds with an
@@ -553,6 +558,11 @@
             IpClient.this.addKeepalivePacketFilter(slot, pkt);
         }
         @Override
+        public void addNattKeepalivePacketFilter(int slot, NattKeepalivePacketDataParcelable pkt) {
+            checkNetworkStackCallingPermission();
+            IpClient.this.addNattKeepalivePacketFilter(slot, pkt);
+        }
+        @Override
         public void removeKeepalivePacketFilter(int slot) {
             checkNetworkStackCallingPermission();
             IpClient.this.removeKeepalivePacketFilter(slot);
@@ -691,11 +701,20 @@
     }
 
     /**
-     * Called by WifiStateMachine to add keepalive packet filter before setting up
+     * Called by WifiStateMachine to add TCP keepalive packet filter before setting up
      * keepalive offload.
      */
     public void addKeepalivePacketFilter(int slot, @NonNull TcpKeepalivePacketDataParcelable pkt) {
-        sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, slot, 0 /* Unused */, pkt);
+        sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, slot, TYPE_TCP, pkt);
+    }
+
+    /**
+     *  Called by WifiStateMachine to add NATT keepalive packet filter before setting up
+     *  keepalive offload.
+     */
+    public void addNattKeepalivePacketFilter(int slot,
+            @NonNull NattKeepalivePacketDataParcelable pkt) {
+        sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, slot, TYPE_NATT, pkt);
     }
 
     /**
@@ -1607,9 +1626,16 @@
 
                 case CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF: {
                     final int slot = msg.arg1;
+                    final int type = msg.arg2;
+
                     if (mApfFilter != null) {
-                        mApfFilter.addKeepalivePacketFilter(slot,
-                                (TcpKeepalivePacketDataParcelable) msg.obj);
+                        if (type == TYPE_NATT) {
+                            mApfFilter.addNattKeepalivePacketFilter(slot,
+                                    (NattKeepalivePacketDataParcelable) msg.obj);
+                        } else {
+                            mApfFilter.addTcpKeepalivePacketFilter(slot,
+                                    (TcpKeepalivePacketDataParcelable) msg.obj);
+                        }
                     }
                     break;
                 }
diff --git a/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java
index a0e508f..93ab3be 100644
--- a/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java
+++ b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java
@@ -1553,7 +1553,7 @@
         parcel.seq = seqNum;
         parcel.ack = ackNum;
 
-        apfFilter.addKeepalivePacketFilter(slot1, parcel);
+        apfFilter.addTcpKeepalivePacketFilter(slot1, parcel);
         program = cb.getApfProgram();
 
         // Verify IPv4 keepalive ack packet is dropped
@@ -1592,7 +1592,7 @@
             ipv6Parcel.seq = seqNum;
             ipv6Parcel.ack = ackNum;
 
-            apfFilter.addKeepalivePacketFilter(slot1, ipv6Parcel);
+            apfFilter.addTcpKeepalivePacketFilter(slot1, ipv6Parcel);
             program = cb.getApfProgram();
 
             // Verify IPv6 keepalive ack packet is dropped
@@ -1614,8 +1614,8 @@
             apfFilter.removeKeepalivePacketFilter(slot1);
 
             // Verify multiple filters
-            apfFilter.addKeepalivePacketFilter(slot1, parcel);
-            apfFilter.addKeepalivePacketFilter(slot2, ipv6Parcel);
+            apfFilter.addTcpKeepalivePacketFilter(slot1, parcel);
+            apfFilter.addTcpKeepalivePacketFilter(slot2, ipv6Parcel);
             program = cb.getApfProgram();
 
             // Verify IPv4 keepalive ack packet is dropped
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/contextual.xml b/packages/SystemUI/res/layout/contextual.xml
index 9b6ccae..90a7768 100644
--- a/packages/SystemUI/res/layout/contextual.xml
+++ b/packages/SystemUI/res/layout/contextual.xml
@@ -42,16 +42,10 @@
         android:layout_height="match_parent"
         android:visibility="invisible"
     />
-    <com.android.systemui.statusbar.policy.KeyButtonView
-        android:id="@+id/rotate_suggestion"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_weight="0"
-        android:scaleType="center"
-        android:visibility="invisible"
-        android:contentDescription="@string/accessibility_rotate_button"
-        android:paddingStart="@dimen/navigation_key_padding"
-        android:paddingEnd="@dimen/navigation_key_padding"
+    <include layout="@layout/rotate_suggestion"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent"
+             android:visibility="invisible"
     />
     <com.android.systemui.statusbar.policy.KeyButtonView
         android:id="@+id/accessibility_button"
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/layout/rotate_suggestion.xml b/packages/SystemUI/res/layout/rotate_suggestion.xml
new file mode 100644
index 0000000..d7f67db
--- /dev/null
+++ b/packages/SystemUI/res/layout/rotate_suggestion.xml
@@ -0,0 +1,29 @@
+<?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
+  -->
+
+<com.android.systemui.statusbar.policy.KeyButtonView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/rotate_suggestion"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_weight="0"
+    android:scaleType="center"
+    android:visibility="invisible"
+    android:contentDescription="@string/accessibility_rotate_button"
+    android:paddingStart="@dimen/navigation_key_padding"
+    android:paddingEnd="@dimen/navigation_key_padding"
+/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/start_contextual.xml b/packages/SystemUI/res/layout/start_contextual.xml
new file mode 100644
index 0000000..e022c73
--- /dev/null
+++ b/packages/SystemUI/res/layout/start_contextual.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             xmlns:systemui="http://schemas.android.com/apk/res-auto"
+             android:id="@+id/start_menu_container"
+             android:layout_width="@dimen/navigation_key_width"
+             android:layout_height="match_parent"
+             android:importantForAccessibility="no"
+             android:focusable="false"
+             android:clipChildren="false"
+             android:clipToPadding="false"
+             >
+    <include layout="@layout/rotate_suggestion"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent"
+             android:visibility="invisible"
+    />
+    <include layout="@layout/back"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent"
+             android:visibility="invisible"
+    />
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 7338687..4abe9f0 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -331,7 +331,7 @@
     <!-- Nav bar button default ordering/layout -->
     <string name="config_navBarLayout" translatable="false">left[.5W],back[1WC];home;recent[1WC],right[.5W]</string>
     <string name="config_navBarLayoutQuickstep" translatable="false">back[1.7WC];home;contextual[1.7WC]</string>
-    <string name="config_navBarLayoutHandle" translatable="false">back[1.7WC];home_handle;ime_switcher[1.7WC]</string>
+    <string name="config_navBarLayoutHandle" translatable="false">start_contextual[.1WC];home_handle;ime_switcher[.1WC]</string>
 
     <bool name="quick_settings_show_full_alarm">false</bool>
 
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 acc03c4..cea336c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1345,6 +1345,7 @@
     <!-- Screen pinning dialog description. -->
     <string name="screen_pinning_description">This keeps it in view until you unpin. Touch &amp; hold Back and Overview to unpin.</string>
     <string name="screen_pinning_description_recents_invisible">This keeps it in view until you unpin. Touch &amp; hold Back and Home to unpin.</string>
+    <string name="screen_pinning_description_gestural">This keeps it in view until you unpin. Swipe up &amp; hold to unpin.</string>
     <!-- Screen pinning dialog description. -->
     <string name="screen_pinning_description_accessible">This keeps it in view until you unpin. Touch &amp; hold Overview to unpin.</string>
     <string name="screen_pinning_description_recents_invisible_accessible">This keeps it in view until you unpin. Touch &amp; hold Home to unpin.</string>
@@ -1658,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 6f44623..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;
@@ -96,4 +91,9 @@
      * Notifies that the accessibility button in the system's navigation area has been long clicked
      */
     void notifyAccessibilityButtonLongClicked() = 16;
+
+    /**
+     * Ends the system screen pinning.
+     */
+    void stopScreenPinning() = 17;
 }
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 3ace705..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;
@@ -35,6 +32,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
 
 import android.annotation.FloatRange;
+import android.app.ActivityTaskManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -60,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;
@@ -102,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;
@@ -118,7 +111,6 @@
 
     private IOverviewProxy mOverviewProxy;
     private int mConnectionBackoffAttempts;
-    private @InteractionType int mInteractionFlags;
     private @SystemUiStateFlags int mSysUiStateFlags;
     private boolean mBound;
     private boolean mIsEnabled;
@@ -151,6 +143,25 @@
         }
 
         @Override
+        public void stopScreenPinning() {
+            if (!verifyCaller("stopScreenPinning")) {
+                return;
+            }
+            long token = Binder.clearCallingIdentity();
+            try {
+                mHandler.post(() -> {
+                    try {
+                        ActivityTaskManager.getService().stopSystemLockTaskMode();
+                    } catch (RemoteException e) {
+                        Log.e(TAG_OPS, "Failed to stop screen pinning");
+                    }
+                });
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
         public void onStatusBarMotionEvent(MotionEvent event) {
             if (!verifyCaller("onStatusBarMotionEvent")) {
                 return;
@@ -213,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;
@@ -360,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();
         }
@@ -459,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());
@@ -633,7 +615,6 @@
     public void addCallback(OverviewProxyListener listener) {
         mConnectionCallbacks.add(listener);
         listener.onConnectionChanged(mOverviewProxy != null);
-        listener.onInteractionFlagsChanged(mInteractionFlags);
         listener.onBackButtonAlphaChanged(mBackButtonAlpha, false);
     }
 
@@ -643,7 +624,7 @@
     }
 
     public boolean shouldShowSwipeUpUI() {
-        return isEnabled() && ((mInteractionFlags & FLAG_DISABLE_SWIPE_UP) == 0);
+        return isEnabled() && !QuickStepContract.isLegacyMode(mNavBarMode);
     }
 
     public boolean isEnabled() {
@@ -654,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)
@@ -673,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);
@@ -745,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());
@@ -755,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/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 07391ed..ade903d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -48,14 +48,17 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 import com.android.systemui.statusbar.phone.NavigationBarView;
+import com.android.systemui.statusbar.phone.NavigationModeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.util.leak.RotationUtils;
 
 import java.util.ArrayList;
 
-public class ScreenPinningRequest implements View.OnClickListener {
+public class ScreenPinningRequest implements View.OnClickListener,
+        NavigationModeController.ModeChangedListener {
 
     private final Context mContext;
 
@@ -64,6 +67,7 @@
     private final OverviewProxyService mOverviewProxyService;
 
     private RequestWindowView mRequestWindow;
+    private int mNavBarMode;
 
     // Id of task to be pinned or locked.
     private int taskId;
@@ -75,6 +79,7 @@
         mWindowManager = (WindowManager)
                 mContext.getSystemService(Context.WINDOW_SERVICE);
         mOverviewProxyService = Dependency.get(OverviewProxyService.class);
+        mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
     }
 
     public void clearPrompt() {
@@ -103,6 +108,11 @@
         mWindowManager.addView(mRequestWindow, lp);
     }
 
+    @Override
+    public void onNavigationModeChanged(int mode) {
+        mNavBarMode = mode;
+    }
+
     public void onConfigurationChanged() {
         if (mRequestWindow != null) {
             mRequestWindow.onConfigurationChanged();
@@ -224,7 +234,9 @@
             mLayout.findViewById(R.id.screen_pinning_text_area)
                     .setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE);
             View buttons = mLayout.findViewById(R.id.screen_pinning_buttons);
-            if (WindowManagerWrapper.getInstance().hasSoftNavigationBar(mContext.getDisplayId())) {
+            WindowManagerWrapper wm = WindowManagerWrapper.getInstance();
+            if (!QuickStepContract.isGesturalMode(mNavBarMode) 
+            	    && wm.hasSoftNavigationBar(mContext.getDisplayId())) {
                 buttons.setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE);
                 swapChildrenIfRtlAndVertical(buttons);
             } else {
@@ -248,7 +260,9 @@
                     && navigationBarView.isRecentsButtonVisible();
             boolean touchExplorationEnabled = mAccessibilityService.isTouchExplorationEnabled();
             int descriptionStringResId;
-            if (recentsVisible) {
+            if (QuickStepContract.isGesturalMode(mNavBarMode)) {
+                descriptionStringResId = R.string.screen_pinning_description_gestural;
+            } else if (recentsVisible) {
                 mLayout.findViewById(R.id.screen_pinning_recents_group).setVisibility(VISIBLE);
                 mLayout.findViewById(R.id.screen_pinning_home_bg_light).setVisibility(INVISIBLE);
                 mLayout.findViewById(R.id.screen_pinning_home_bg).setVisibility(INVISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 21f0c1e..fcbb416 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -143,11 +143,6 @@
             StatusBarStateController statusBarStateController,
             KeyguardUpdateMonitor keyguardUpdateMonitor) {
         mContext = context;
-        mIndicationArea = indicationArea;
-        mTextView = indicationArea.findViewById(R.id.keyguard_indication_text);
-        mInitialTextColorState = mTextView != null ?
-                mTextView.getTextColors() : ColorStateList.valueOf(Color.WHITE);
-        mDisclosure = indicationArea.findViewById(R.id.keyguard_indication_enterprise_disclosure);
         mLockIcon = lockIcon;
         mShadeController = shadeController;
         mAccessibilityController = accessibilityController;
@@ -172,6 +167,7 @@
 
         mDevicePolicyManager = (DevicePolicyManager) context.getSystemService(
                 Context.DEVICE_POLICY_SERVICE);
+        setIndicationArea(indicationArea);
         updateDisclosure();
 
         mKeyguardUpdateMonitor.registerCallback(getKeyguardCallback());
@@ -180,17 +176,13 @@
         mUnlockMethodCache.addListener(this);
     }
 
-    /**
-     * Used by {@link com.android.systemui.statusbar.phone.StatusBar} to give the indication
-     * controller a chance to unregister itself as a receiver.
-     *
-     * //TODO: This can probably be converted to a fragment and not have to be manually recreated
-     */
-    public void destroy() {
-        mKeyguardUpdateMonitor.removeCallback(mTickReceiver);
-        mKeyguardUpdateMonitor.removeCallback(getKeyguardCallback());
-        mStatusBarStateController.removeCallback(this);
-        mUnlockMethodCache.removeListener(this);
+    public void setIndicationArea(ViewGroup indicationArea) {
+        mIndicationArea = indicationArea;
+        mTextView = indicationArea.findViewById(R.id.keyguard_indication_text);
+        mInitialTextColorState = mTextView != null ?
+                mTextView.getTextColors() : ColorStateList.valueOf(Color.WHITE);
+        mDisclosure = indicationArea.findViewById(R.id.keyguard_indication_enterprise_disclosure);
+        updateIndication(false /* animate */);
     }
 
     private boolean handleLockLongClick(View view) {
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/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
index 5f5fad3..6a93c7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
@@ -47,7 +47,7 @@
     private Boolean mLongClickable;
     private Float mAlpha;
     private Float mDarkIntensity;
-    private Integer mVisibility = -1;
+    private Integer mVisibility = View.VISIBLE;
     private Boolean mDelayTouchFeedback;
     private KeyButtonDrawable mImageDrawable;
     private View mCurrentView;
@@ -86,7 +86,7 @@
         if (mAlpha != null) {
             view.setAlpha(mAlpha);
         }
-        if (mVisibility != null && mVisibility != -1) {
+        if (mVisibility != null) {
             view.setVisibility(mVisibility);
         }
         if (mAccessibilityDelegate != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButton.java
index 541c142..5bc17f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButton.java
@@ -51,7 +51,7 @@
      * Reload the drawable from resource id, should reapply the previous dark intensity.
      */
     public void updateIcon() {
-        if (getCurrentView() == null || !getCurrentView().isAttachedToWindow()) {
+        if (getCurrentView() == null || !getCurrentView().isAttachedToWindow() || mIconResId == 0) {
             return;
         }
         final KeyButtonDrawable currentDrawable = getImageDrawable();
@@ -92,7 +92,7 @@
             setVisibility(View.VISIBLE);
             return true;
         }
-        return mGroup.setButtonVisiblity(getId(), true /* visible */) == View.VISIBLE;
+        return mGroup.setButtonVisibility(getId(), true /* visible */) == View.VISIBLE;
     }
 
     /**
@@ -104,7 +104,7 @@
             setVisibility(View.INVISIBLE);
             return false;
         }
-        return mGroup.setButtonVisiblity(getId(), false /* visible */) != View.VISIBLE;
+        return mGroup.setButtonVisibility(getId(), false /* visible */) != View.VISIBLE;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButtonGroup.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButtonGroup.java
index 02b660f..9e843f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButtonGroup.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButtonGroup.java
@@ -37,7 +37,7 @@
     /**
      * Add a contextual button to the group. The order of adding increases in its priority. The
      * priority is used to determine which button should be visible when setting multiple button's
-     * visibility {@see setButtonVisiblity}.
+     * visibility {@see setButtonVisibility}.
      * @param button the button added to the group
      */
     public void addButton(@NonNull ContextualButton button) {
@@ -71,7 +71,7 @@
      * @return if the button is visible after operation
      * @throws RuntimeException if the input id does not match any of the ids in the group
      */
-    public int setButtonVisiblity(@IdRes int buttonResId, boolean visible) {
+    public int setButtonVisibility(@IdRes int buttonResId, boolean visible) {
         final int index = getContextButtonIndex(buttonResId);
         if (index == INVALID_INDEX) {
             throw new RuntimeException("Cannot find the button id of " + buttonResId
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 5b464a9..80d36a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -133,7 +133,6 @@
     private LockPatternUtils mLockPatternUtils;
     private FlashlightController mFlashlightController;
     private PreviewInflater mPreviewInflater;
-    private KeyguardIndicationController mIndicationController;
     private AccessibilityController mAccessibilityController;
     private StatusBar mStatusBar;
     private KeyguardAffordanceHelper mAffordanceHelper;
@@ -221,7 +220,6 @@
     };
 
     public void initFrom(KeyguardBottomAreaView oldBottomArea) {
-        setKeyguardIndicationController(oldBottomArea.mIndicationController);
         setStatusBar(oldBottomArea.mStatusBar);
     }
 
@@ -707,15 +705,6 @@
                 }
             };
 
-    public void setKeyguardIndicationController(
-            KeyguardIndicationController keyguardIndicationController) {
-        mIndicationController = keyguardIndicationController;
-    }
-
-    public void showTransientIndication(int id) {
-        mIndicationController.showTransientIndication(id);
-    }
-
     public void updateLeftAffordance() {
         updateLeftAffordanceIcon();
         updateLeftPreview();
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/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index 404c07b..963fc54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -71,6 +71,7 @@
     public static final String RIGHT = "right";
     public static final String CONTEXTUAL = "contextual";
     public static final String IME_SWITCHER = "ime_switcher";
+    public static final String START_CONTEXTUAL = "start_contextual";
 
     public static final String GRAVITY_SEPARATOR = ";";
     public static final String BUTTON_SEPARATOR = ",";
@@ -419,6 +420,8 @@
             v = inflater.inflate(R.layout.home_handle, parent, false);
         } else if (IME_SWITCHER.equals(button)) {
             v = inflater.inflate(R.layout.ime_switcher, parent, false);
+        } else if (START_CONTEXTUAL.equals(button)) {
+            v = inflater.inflate(R.layout.start_contextual, parent, false);
         } else if (button.startsWith(KEY)) {
             String uri = extractImage(button);
             int code = extractKeycode(button);
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 6f1e161..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;
 
@@ -49,6 +48,8 @@
 import android.view.Surface;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewTreeObserver.InternalInsetsInfo;
+import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
@@ -135,6 +136,7 @@
     private boolean mImeVisible;
 
     private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>();
+    private final ContextualButtonGroup mStartContextualButtonGroup;
     private final ContextualButtonGroup mContextualButtonGroup;
     private Configuration mConfiguration;
     private Configuration mTmpLastConfiguration;
@@ -233,11 +235,36 @@
         }
     };
 
+    private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener = info -> {
+        // When the nav bar is in 2-button or 3-button mode, or when IME is visible in fully
+        // gestural mode, the entire nav bar should be touchable.
+        if (!QuickStepContract.isGesturalMode(mNavBarMode) || mImeVisible) {
+            info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+            return;
+        }
+        info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+        RotationContextButton rotationContextButton = getRotateSuggestionButton();
+        // If the rotate suggestion button is not visible in fully gestural mode, the entire nav bar
+        // is not touchable so that the app underneath can be clicked through.
+        if (rotationContextButton.getVisibility() != VISIBLE) {
+            info.touchableRegion.setEmpty();
+        } else {
+            // Set the rotate suggestion button area to be touchable.
+            rotationContextButton.getCurrentView().getLocationInWindow(mTmpPosition);
+            Rect rect = new Rect(mTmpPosition[0], mTmpPosition[1],
+                    mTmpPosition[0] + mRotationButtonBounds.width(),
+                    mTmpPosition[1] + mRotationButtonBounds.height());
+            info.touchableRegion.union(rect);
+        }
+    };
+
     public NavigationBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
         mIsVertical = false;
         mLongClickableAccessibilityButton = false;
+        mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
+        boolean isGesturalMode = QuickStepContract.isGesturalMode(mNavBarMode);
 
         // Set up the context group of buttons
         mContextualButtonGroup = new ContextualButtonGroup(R.id.menu_container);
@@ -253,12 +280,21 @@
                         R.drawable.ic_sysbar_accessibility_button);
         mContextualButtonGroup.addButton(menuButton);
         mContextualButtonGroup.addButton(imeSwitcherButton);
-        mContextualButtonGroup.addButton(rotateSuggestionButton);
+        if (!isGesturalMode) {
+            mContextualButtonGroup.addButton(rotateSuggestionButton);
+        }
         mContextualButtonGroup.addButton(accessibilityButton);
 
         mOverviewProxyService = Dependency.get(OverviewProxyService.class);
         mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService);
 
+        final ContextualButton backButton = new ContextualButton(R.id.back, 0);
+        mStartContextualButtonGroup = new ContextualButtonGroup(R.id.start_menu_container);
+        if (isGesturalMode) {
+            mStartContextualButtonGroup.addButton(rotateSuggestionButton);
+        }
+        mStartContextualButtonGroup.addButton(backButton);
+
         mConfiguration = new Configuration();
         mTmpLastConfiguration = new Configuration();
         mConfiguration.updateFrom(context.getResources().getConfiguration());
@@ -266,7 +302,7 @@
         mScreenPinningNotify = new ScreenPinningNotify(mContext);
         mBarTransitions = new NavigationBarTransitions(this);
 
-        mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back));
+        mButtonDispatchers.put(R.id.back, backButton);
         mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home));
         mButtonDispatchers.put(R.id.home_handle, new ButtonDispatcher(R.id.home_handle));
         mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
@@ -275,6 +311,7 @@
         mButtonDispatchers.put(R.id.accessibility_button, accessibilityButton);
         mButtonDispatchers.put(R.id.rotate_suggestion, rotateSuggestionButton);
         mButtonDispatchers.put(R.id.menu_container, mContextualButtonGroup);
+        mButtonDispatchers.put(R.id.start_menu_container, mStartContextualButtonGroup);
         mDeadZone = new DeadZone(this);
 
         mEdgeBackGestureHandler = new EdgeBackGestureHandler(context, mOverviewProxyService);
@@ -390,8 +427,11 @@
     }
 
     public RotationContextButton getRotateSuggestionButton() {
-        return (RotationContextButton) mContextualButtonGroup
-                .getContextButton(R.id.rotate_suggestion);
+        return (RotationContextButton) mButtonDispatchers.get(R.id.rotate_suggestion);
+    }
+
+    public ContextualButtonGroup getStartContextualButtonGroup() {
+        return mStartContextualButtonGroup;
     }
 
     public ButtonDispatcher getHomeHandle() {
@@ -430,6 +470,7 @@
         if (densityChange || dirChange) {
             mRecentIcon = getDrawable(R.drawable.ic_sysbar_recent);
             mContextualButtonGroup.updateIcons();
+            mStartContextualButtonGroup.updateIcons();
         }
         if (orientationChange || densityChange || dirChange) {
             mBackIcon = getBackDrawable();
@@ -437,12 +478,16 @@
     }
 
     public KeyButtonDrawable getBackDrawable() {
-        KeyButtonDrawable drawable = chooseNavigationIconDrawable(R.drawable.ic_sysbar_back,
-                R.drawable.ic_sysbar_back_quick_step);
+        KeyButtonDrawable drawable = getDrawable(getBackDrawableRes());
         orientBackButton(drawable);
         return drawable;
     }
 
+    public @DrawableRes int getBackDrawableRes() {
+        return chooseNavigationIconDrawableRes(R.drawable.ic_sysbar_back,
+                R.drawable.ic_sysbar_back_quick_step);
+    }
+
     public KeyButtonDrawable getHomeDrawable() {
         final boolean quickStepEnabled = mOverviewProxyService.shouldShowSwipeUpUI();
         KeyButtonDrawable drawable = quickStepEnabled
@@ -485,8 +530,13 @@
 
     private KeyButtonDrawable chooseNavigationIconDrawable(@DrawableRes int icon,
             @DrawableRes int quickStepIcon) {
+        return getDrawable(chooseNavigationIconDrawableRes(icon, quickStepIcon));
+    }
+
+    private @DrawableRes int chooseNavigationIconDrawableRes(@DrawableRes int icon,
+            @DrawableRes int quickStepIcon) {
         final boolean quickStepEnabled = mOverviewProxyService.shouldShowSwipeUpUI();
-        return quickStepEnabled ? getDrawable(quickStepIcon) : getDrawable(icon);
+        return quickStepEnabled ? quickStepIcon : icon;
     }
 
     private KeyButtonDrawable getDrawable(@DrawableRes int icon) {
@@ -527,7 +577,6 @@
             mTransitionListener.onBackAltCleared();
         }
         mImeVisible = visible;
-        updateWindowTouchable();
     }
 
     public void setDisabledFlags(int disabledFlags) {
@@ -564,7 +613,7 @@
         updateRecentsIcon();
 
         // Update IME button visibility, a11y and rotate button always overrides the appearance
-        mContextualButtonGroup.setButtonVisiblity(R.id.ime_switcher,
+        mContextualButtonGroup.setButtonVisibility(R.id.ime_switcher,
                 (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0);
 
         mBarTransitions.reapplyDarkIntensity();
@@ -587,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;
             }
@@ -609,6 +654,7 @@
         }
 
         getBackButton().setVisibility(disableBack      ? View.INVISIBLE : View.VISIBLE);
+        mStartContextualButtonGroup.setButtonVisibility(R.id.back, !disableBack);
         getHomeButton().setVisibility(disableHome      ? View.INVISIBLE : View.VISIBLE);
         getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
     }
@@ -714,11 +760,6 @@
         setWindowFlag(WindowManager.LayoutParams.FLAG_SLIPPERY, slippery);
     }
 
-    public void updateWindowTouchable() {
-        boolean touchable = mImeVisible || !QuickStepContract.isGesturalMode(mNavBarMode);
-        setWindowFlag(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, !touchable);
-    }
-
     private void setWindowFlag(int flags, boolean enable) {
         final ViewGroup navbarView = ((ViewGroup) getParent());
         if (navbarView == null) {
@@ -743,6 +784,7 @@
         mBarTransitions.onNavigationModeChanged(mNavBarMode);
         mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode);
         mRecentsOnboarding.onNavigationModeChanged(mNavBarMode);
+        getRotateSuggestionButton().onNavigationModeChanged(mNavBarMode);
 
         // Color adaption is tied with showing home handle, only avaliable if visible
         mTintController.onNavigationModeChanged(mNavBarMode);
@@ -751,17 +793,16 @@
         } else {
             mTintController.stop();
         }
-        updateWindowTouchable();
     }
 
     public void setMenuVisibility(final boolean show) {
-        mContextualButtonGroup.setButtonVisiblity(R.id.menu, show);
+        mContextualButtonGroup.setButtonVisibility(R.id.menu, show);
     }
 
     public void setAccessibilityButtonState(final boolean visible, final boolean longClickable) {
         mLongClickableAccessibilityButton = longClickable;
         getAccessibilityButton().setLongClickable(longClickable);
-        mContextualButtonGroup.setButtonVisiblity(R.id.accessibility_button, visible);
+        mContextualButtonGroup.setButtonVisibility(R.id.accessibility_button, visible);
     }
 
     void hideRecentsOnboarding() {
@@ -1044,12 +1085,11 @@
         onPluginDisconnected(null); // Create default gesture helper
         Dependency.get(PluginManager.class).addPluginListener(this,
                 NavGesture.class, false /* Only one */);
-        int navBarMode = Dependency.get(NavigationModeController.class).addListener(this);
-        onNavigationModeChanged(navBarMode);
+        onNavigationModeChanged(mNavBarMode);
         setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled());
 
         mEdgeBackGestureHandler.onNavBarAttached();
-        updateWindowTouchable();
+        getViewTreeObserver().addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
     }
 
     @Override
@@ -1065,6 +1105,8 @@
             mButtonDispatchers.valueAt(i).onDestroy();
         }
         mEdgeBackGestureHandler.onNavBarDetached();
+        getViewTreeObserver().removeOnComputeInternalInsetsListener(
+                mOnComputeInternalInsetsListener);
     }
 
     private void setUpSwipeUpOnboarding(boolean connectedToOverviewProxy) {
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/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index c39a494..7b3ddf7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -337,6 +337,7 @@
      * work, check the current id with the cached id.
      */
     private int mThemeResId;
+    private KeyguardIndicationController mKeyguardIndicationController;
 
     @Inject
     public NotificationPanelView(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
@@ -516,6 +517,7 @@
         mKeyguardBottomArea.initFrom(oldBottomArea);
         addView(mKeyguardBottomArea, index);
         initBottomArea();
+        mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
         onDozeAmountChanged(mStatusBarStateController.getDozeAmount(),
                 mStatusBarStateController.getInterpolatedDozeAmount());
 
@@ -535,7 +537,8 @@
     }
 
     public void setKeyguardIndicationController(KeyguardIndicationController indicationController) {
-        mKeyguardBottomArea.setKeyguardIndicationController(indicationController);
+        mKeyguardIndicationController = indicationController;
+        mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
     }
 
     @Override
@@ -3045,7 +3048,7 @@
     }
 
     public void showTransientIndication(int id) {
-        mKeyguardBottomArea.showTransientIndication(id);
+        mKeyguardIndicationController.showTransientIndication(id);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java
index e887f5b..7203e57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
+
 import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION;
 
 import android.animation.Animator;
@@ -45,6 +47,7 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.statusbar.policy.KeyButtonDrawable;
 import com.android.systemui.statusbar.policy.RotationLockController;
@@ -52,7 +55,9 @@
 import java.util.Optional;
 import java.util.function.Consumer;
 
-public class RotationContextButton extends ContextualButton {
+/** Containing logic for the rotation button in nav bar. */
+public class RotationContextButton extends ContextualButton implements
+        NavigationModeController.ModeChangedListener {
     public static final boolean DEBUG_ROTATION = false;
 
     private static final int BUTTON_FADE_IN_OUT_DURATION_MS = 100;
@@ -76,6 +81,7 @@
             () -> mPendingRotationSuggestion = false;
     private Animator mRotateHideAnimator;
     private boolean mAccessibilityFeedbackEnabled;
+    private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
 
     private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
     private final ViewRippler mViewRippler = new ViewRippler();
@@ -304,7 +310,8 @@
     @Override
     protected KeyButtonDrawable getNewDrawable() {
         Context context = new ContextThemeWrapper(getContext().getApplicationContext(), mStyleRes);
-        return KeyButtonDrawable.create(context, mIconResId, false /* shadow */);
+        return KeyButtonDrawable.create(context, mIconResId, false /* shadow */,
+                QuickStepContract.isGesturalMode(mNavBarMode));
     }
 
     @Override
@@ -390,9 +397,9 @@
     }
 
     private int computeRotationProposalTimeout() {
-        if (mAccessibilityFeedbackEnabled) return 20000;
-        if (mHoveringRotationSuggestion) return 16000;
-        return 10000;
+        if (mAccessibilityFeedbackEnabled) return 10000;
+        if (mHoveringRotationSuggestion) return 8000;
+        return 5000;
     }
 
     private boolean isRotateSuggestionIntroduced() {
@@ -414,6 +421,11 @@
         }
     }
 
+    @Override
+    public void onNavigationModeChanged(int mode) {
+        mNavBarMode = mode;
+    }
+
     private class TaskStackListenerImpl extends TaskStackChangeListener {
         // Invalidate any rotation suggestion on task change or activity orientation change
         // Note: all callbacks happen on main thread
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 2babfe3..93db82d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1166,18 +1166,6 @@
 
     @Override
     public void onThemeChanged() {
-        // Recreate Indication controller because internal references changed
-        if (mKeyguardIndicationController != null) {
-            mKeyguardIndicationController.destroy();
-        }
-        mKeyguardIndicationController =
-                SystemUIFactory.getInstance().createKeyguardIndicationController(mContext,
-                        mStatusBarWindow.findViewById(R.id.keyguard_indication_area),
-                        mStatusBarWindow.findViewById(R.id.lock_icon));
-        mNotificationPanel.setKeyguardIndicationController(mKeyguardIndicationController);
-        mKeyguardIndicationController
-                .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
-        mKeyguardIndicationController.setVisible(mState == StatusBarState.KEYGUARD);
         if (mStatusBarKeyguardViewManager != null) {
             mStatusBarKeyguardViewManager.onThemeChanged();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 002313c..7fe8906 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -25,8 +25,6 @@
 import android.content.res.ColorStateList;
 import android.os.Bundle;
 import android.os.SystemClock;
-import android.transition.Fade;
-import android.transition.TransitionManager;
 import android.util.StatsLog;
 import android.view.KeyEvent;
 import android.view.View;
@@ -40,10 +38,8 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.settingslib.animation.AppearAnimationUtils;
-import com.android.settingslib.animation.DisappearAnimationUtils;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dependency;
-import com.android.systemui.Interpolators;
 import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.dock.DockManager;
@@ -51,6 +47,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
@@ -158,7 +155,7 @@
     private boolean mLastPulsing;
     private int mLastBiometricMode;
     private boolean mGoingToSleepVisibleNotOccluded;
-    private int mLastLockVisibility = -1;
+    private boolean mLastLockVisible;
 
     private OnDismissAction mAfterKeyguardGoneAction;
     private final ArrayList<Runnable> mAfterKeyguardGoneRunnables = new ArrayList<>();
@@ -213,6 +210,9 @@
         mStatusBar = statusBar;
         mContainer = container;
         mLockIconContainer = lockIconContainer;
+        if (mLockIconContainer != null) {
+            mLastLockVisible = mLockIconContainer.getVisibility() == View.VISIBLE;
+        }
         mBiometricUnlockController = biometricUnlockController;
         mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
                 mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry,
@@ -261,21 +261,20 @@
         }
         boolean keyguardWithoutQs = mStatusBarStateController.getState() == StatusBarState.KEYGUARD
                 && !mNotificationPanelView.isQsExpanded();
-        int lockVisibility = (mBouncer.isShowing() || keyguardWithoutQs)
-                && !mBouncer.isAnimatingAway() ? View.VISIBLE : View.INVISIBLE;
+        boolean lockVisible = (mBouncer.isShowing() || keyguardWithoutQs)
+                && !mBouncer.isAnimatingAway();
 
-        if (mLastLockVisibility != lockVisibility) {
-            mLastLockVisibility = lockVisibility;
-
-            Fade transition = new Fade();
-            boolean appearing = lockVisibility == View.VISIBLE;
-            transition.setDuration(appearing ? AppearAnimationUtils.DEFAULT_APPEAR_DURATION
-                    : DisappearAnimationUtils.DEFAULT_APPEAR_DURATION / 2);
-            transition.setInterpolator(appearing ? Interpolators.ALPHA_IN
-                    : Interpolators.ALPHA_OUT);
-            TransitionManager.beginDelayedTransition((ViewGroup) mLockIconContainer.getParent(),
-                    transition);
-            mLockIconContainer.setVisibility(lockVisibility);
+        if (mLastLockVisible != lockVisible) {
+            mLastLockVisible = lockVisible;
+            if (lockVisible) {
+                CrossFadeHelper.fadeIn(mLockIconContainer,
+                        AppearAnimationUtils.DEFAULT_APPEAR_DURATION /* duration */,
+                        0 /* delay */);
+            } else {
+                CrossFadeHelper.fadeOut(mLockIconContainer,
+                        AppearAnimationUtils.DEFAULT_APPEAR_DURATION / 2 /* duration */,
+                        0 /* delay */, null /* runnable */);
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
index dd0c344..2bfc311 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
@@ -30,9 +30,11 @@
 import android.graphics.ColorFilter;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
 import android.graphics.PorterDuff.Mode;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.Drawable;
 import android.util.FloatProperty;
@@ -77,13 +79,14 @@
 
     private final Paint mIconPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
     private final Paint mShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+    private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
     private final ShadowDrawableState mState;
     private AnimatedVectorDrawable mAnimatedDrawable;
 
     public KeyButtonDrawable(Drawable d, @ColorInt int lightColor, @ColorInt int darkColor,
-            boolean horizontalFlip) {
+            boolean horizontalFlip, boolean hasOvalBg) {
         this(d, new ShadowDrawableState(lightColor, darkColor,
-                d instanceof AnimatedVectorDrawable, horizontalFlip));
+                d instanceof AnimatedVectorDrawable, horizontalFlip, hasOvalBg));
     }
 
     private KeyButtonDrawable(Drawable d, ShadowDrawableState state) {
@@ -98,6 +101,7 @@
             mAnimatedDrawable = (AnimatedVectorDrawable) mState.mChildState.newDrawable().mutate();
             setDrawableBounds(mAnimatedDrawable);
         }
+        mOvalBgPaint.setColor(mState.mDarkColor);
     }
 
     public void setDarkIntensity(float intensity) {
@@ -165,7 +169,12 @@
     public void setColorFilter(ColorFilter colorFilter) {
         mIconPaint.setColorFilter(colorFilter);
         if (mAnimatedDrawable != null) {
-            mAnimatedDrawable.setColorFilter(colorFilter);
+            if (mState.mHasOvalBg) {
+                mAnimatedDrawable.setColorFilter(
+                        new PorterDuffColorFilter(mState.mLightColor, PorterDuff.Mode.SRC_IN));
+            } else {
+                mAnimatedDrawable.setColorFilter(colorFilter);
+            }
         }
         invalidateSelf();
     }
@@ -235,6 +244,10 @@
             return;
         }
 
+        if (mState.mHasOvalBg) {
+            canvas.drawOval(new RectF(bounds), mOvalBgPaint);
+        }
+
         if (mAnimatedDrawable != null) {
             mAnimatedDrawable.draw(canvas);
         } else {
@@ -379,14 +392,16 @@
         final int mLightColor;
         final int mDarkColor;
         final boolean mSupportsAnimation;
+        final boolean mHasOvalBg;
 
         public ShadowDrawableState(@ColorInt int lightColor, @ColorInt int darkColor,
-                boolean animated, boolean horizontalFlip) {
+                boolean animated, boolean horizontalFlip, boolean hasOvalBg) {
             mLightColor = lightColor;
             mDarkColor = darkColor;
             mSupportsAnimation = animated;
             mAlpha = 255;
             mHorizontalFlip = horizontalFlip;
+            mHasOvalBg = hasOvalBg;
         }
 
         @Override
@@ -411,32 +426,51 @@
      * @param ctx Context to get the drawable and determine the dark and light theme
      * @param icon the icon resource id
      * @param hasShadow if a shadow will appear with the drawable
+     * @param hasOvalBg if an oval bg will be drawn
      * @return KeyButtonDrawable
      */
     public static KeyButtonDrawable create(@NonNull Context ctx, @DrawableRes int icon,
-            boolean hasShadow) {
+            boolean hasShadow, boolean hasOvalBg) {
         final int dualToneDarkTheme = Utils.getThemeAttr(ctx, R.attr.darkIconTheme);
         final int dualToneLightTheme = Utils.getThemeAttr(ctx, R.attr.lightIconTheme);
         Context lightContext = new ContextThemeWrapper(ctx, dualToneLightTheme);
         Context darkContext = new ContextThemeWrapper(ctx, dualToneDarkTheme);
-        return KeyButtonDrawable.create(lightContext, darkContext, icon, hasShadow);
+        return KeyButtonDrawable.create(lightContext, darkContext, icon, hasShadow, hasOvalBg);
     }
 
+    /**
+     * Creates a KeyButtonDrawable with a shadow given its icon. For more information, see
+     * {@link #create(Context, int, boolean, boolean)}.
+     */
+    public static KeyButtonDrawable create(@NonNull Context ctx, @DrawableRes int icon,
+            boolean hasShadow) {
+        return create(ctx, icon, hasShadow, false /* hasOvalBg */);
+    }
+
+    /**
+     * Creates a KeyButtonDrawable with a shadow given its icon. For more information, see
+     * {@link #create(Context, int, boolean, boolean)}.
+     */
     public static KeyButtonDrawable create(Context lightContext, Context darkContext,
-            @DrawableRes int iconResId, boolean hasShadow) {
+            @DrawableRes int iconResId, boolean hasShadow, boolean hasOvalBg) {
         return create(lightContext,
             Utils.getColorAttrDefaultColor(lightContext, R.attr.singleToneColor),
             Utils.getColorAttrDefaultColor(darkContext, R.attr.singleToneColor),
-            iconResId, hasShadow);
+            iconResId, hasShadow, hasOvalBg);
     }
 
+    /**
+     * Creates a KeyButtonDrawable with a shadow given its icon. For more information, see
+     * {@link #create(Context, int, boolean, boolean)}.
+     */
     public static KeyButtonDrawable create(Context context, @ColorInt int lightColor,
-        @ColorInt int darkColor, @DrawableRes int iconResId, boolean hasShadow) {
+            @ColorInt int darkColor, @DrawableRes int iconResId, boolean hasShadow,
+            boolean hasOvalBg) {
         final Resources res = context.getResources();
         boolean isRtl = res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
         Drawable d = context.getDrawable(iconResId);
         final KeyButtonDrawable drawable = new KeyButtonDrawable(d, lightColor, darkColor,
-                isRtl && d.isAutoMirrored());
+                isRtl && d.isAutoMirrored(), hasOvalBg);
         if (hasShadow) {
             int offsetX = res.getDimensionPixelSize(R.dimen.nav_key_button_shadow_offset_x);
             int offsetY = res.getDimensionPixelSize(R.dimen.nav_key_button_shadow_offset_y);
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/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index ac536a5b..2858ba9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -284,10 +284,5 @@
         verify(mUnlockMethodCache).addListener(eq(mController));
         verify(mStatusBarStateController).addCallback(eq(mController));
         verify(mKeyguardUpdateMonitor, times(2)).registerCallback(any());
-
-        mController.destroy();
-        verify(mUnlockMethodCache).removeListener(eq(mController));
-        verify(mStatusBarStateController).removeCallback(eq(mController));
-        verify(mKeyguardUpdateMonitor, times(2)).removeCallback(any());
     }
 }
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..7bd2580 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/phone/KeyguardBottomAreaTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
index b95bb0a..688a6fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
@@ -22,8 +22,6 @@
 
     @Mock
     private lateinit var mStatusBar: StatusBar
-    @Mock
-    private lateinit var mKeyguardIndicationController: KeyguardIndicationController
     private lateinit var mKeyguardBottomArea: KeyguardBottomAreaView
 
     @Before
@@ -32,7 +30,6 @@
         mKeyguardBottomArea = LayoutInflater.from(mContext).inflate(
                 R.layout.keyguard_bottom_area, null, false) as KeyguardBottomAreaView
         mKeyguardBottomArea.setStatusBar(mStatusBar)
-        mKeyguardBottomArea.setKeyguardIndicationController(mKeyguardIndicationController)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java
index c837c9c..cb70a1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java
@@ -80,7 +80,7 @@
 
     @Test
     public void testSetButtonVisibility() throws Exception {
-        assertFalse("By default the group should be invisible.", mGroup.isVisible());
+        assertTrue("By default the group should be visible.", mGroup.isVisible());
 
         // Set button 1 to be visible, make sure it is the only visible button
         showButton(mBtn1);
@@ -89,7 +89,7 @@
         assertFalse(mBtn2.isVisible());
 
         // Hide button 1 and make sure the group is also invisible
-        assertNotEquals(mGroup.setButtonVisiblity(BUTTON_1_ID, false /* visible */), View.VISIBLE);
+        assertNotEquals(mGroup.setButtonVisibility(BUTTON_1_ID, false /* visible */), View.VISIBLE);
         assertFalse("No buttons are visible, group should also be hidden", mGroup.isVisible());
         assertNull("No buttons should be visible", mGroup.getVisibleContextButton());
     }
@@ -97,7 +97,7 @@
     @Test(expected = RuntimeException.class)
     public void testSetButtonVisibilityUnaddedButton() throws Exception {
         int id = mBtn2.getId() + 1;
-        mGroup.setButtonVisiblity(id, true /* visible */);
+        mGroup.setButtonVisibility(id, true /* visible */);
         fail("Did not throw when setting a button with an invalid id");
     }
 
@@ -120,17 +120,17 @@
         assertTrue(mGroup.isButtonVisibleWithinGroup(mBtn2.getId()));
 
         // Hide button 2
-        assertNotEquals(mGroup.setButtonVisiblity(BUTTON_2_ID, false /* visible */), View.VISIBLE);
+        assertNotEquals(mGroup.setButtonVisibility(BUTTON_2_ID, false /* visible */), View.VISIBLE);
         assertEquals("Hiding button 2 should show button 1", mBtn1,
                 mGroup.getVisibleContextButton());
 
         // Hide button 1
-        assertNotEquals(mGroup.setButtonVisiblity(BUTTON_1_ID, false /* visible */), View.VISIBLE);
+        assertNotEquals(mGroup.setButtonVisibility(BUTTON_1_ID, false /* visible */), View.VISIBLE);
         assertEquals("Hiding button 1 should show button 0", mBtn0,
                 mGroup.getVisibleContextButton());
 
         // Hide button 0, all buttons are now invisible
-        assertNotEquals(mGroup.setButtonVisiblity(BUTTON_0_ID, false /* visible */), View.VISIBLE);
+        assertNotEquals(mGroup.setButtonVisibility(BUTTON_0_ID, false /* visible */), View.VISIBLE);
         assertFalse("No buttons are visible, group should also be invisible", mGroup.isVisible());
         assertNull(mGroup.getVisibleContextButton());
         assertFalse(mGroup.isButtonVisibleWithinGroup(mBtn0.getId()));
@@ -144,7 +144,7 @@
         showButton(mBtn2);
 
         // Show button 1
-        assertNotEquals(mGroup.setButtonVisiblity(BUTTON_1_ID, true /* visible */), View.VISIBLE);
+        assertNotEquals(mGroup.setButtonVisibility(BUTTON_1_ID, true /* visible */), View.VISIBLE);
         assertTrue("Showing button 1 lower priority should be hidden but visible underneath",
                 mGroup.isButtonVisibleWithinGroup(BUTTON_1_ID));
         assertFalse(mBtn0.isVisible());
@@ -152,7 +152,7 @@
         assertTrue(mBtn2.isVisible());
 
         // Hide button 1
-        assertNotEquals(mGroup.setButtonVisiblity(BUTTON_1_ID, false /* visible */), View.VISIBLE);
+        assertNotEquals(mGroup.setButtonVisibility(BUTTON_1_ID, false /* visible */), View.VISIBLE);
         assertFalse("Hiding button 1 with lower priority hides itself underneath",
                 mGroup.isButtonVisibleWithinGroup(BUTTON_1_ID));
         assertTrue("A button still visible, group should also be visible", mGroup.isVisible());
@@ -180,9 +180,9 @@
         final Drawable d = mock(Drawable.class);
         final ContextualButton button = spy(mBtn0);
         final KeyButtonDrawable kbd1 = spy(new KeyButtonDrawable(d, unusedColor, unusedColor,
-                false /* horizontalFlip */));
+                false /* horizontalFlip */, false /* hasOvalBg */));
         final KeyButtonDrawable kbd2 = spy(new KeyButtonDrawable(d, unusedColor, unusedColor,
-                false /* horizontalFlip */));
+                false /* horizontalFlip */, false /* hasOvalBg */));
         kbd1.setDarkIntensity(TEST_DARK_INTENSITY);
         kbd2.setDarkIntensity(0f);
 
@@ -198,7 +198,7 @@
     }
 
     private void showButton(ContextualButton button) {
-        assertEquals(View.VISIBLE, mGroup.setButtonVisiblity(button.getId(), true /* visible */));
+        assertEquals(View.VISIBLE, mGroup.setButtonVisibility(button.getId(), true /* visible */));
         assertTrue("After set a button visible, group should also be visible", mGroup.isVisible());
         assertEquals(button, mGroup.getVisibleContextButton());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index d09cea5..1c6e3b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -28,7 +28,6 @@
 import android.content.Context;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.view.View;
 import android.view.ViewGroup;
 
 import androidx.test.filters.SmallTest;
@@ -39,7 +38,6 @@
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.StatusBarState;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -198,29 +196,6 @@
         verify(mBouncer, never()).setExpansion(anyFloat());
     }
 
-    @Test
-    public void onQsExpansionChanged_lockVisibleOnlyWhenCollapsed() {
-        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
-        mStatusBarKeyguardViewManager.onQsExpansionChanged(0);
-        verify(mLockIconContainer).setVisibility(eq(View.VISIBLE));
-
-        reset(mNotificationPanelView);
-        when(mNotificationPanelView.isQsExpanded()).thenReturn(true);
-        mStatusBarKeyguardViewManager.onQsExpansionChanged(1f);
-        verify(mLockIconContainer).setVisibility(eq(View.INVISIBLE));
-    }
-
-    @Test
-    public void onQsExpansionChanged_lockInvisibleWhenAnimatingAway() {
-        when(mBouncer.isShowing()).thenReturn(true);
-        mStatusBarKeyguardViewManager.onQsExpansionChanged(0);
-        verify(mLockIconContainer).setVisibility(eq(View.VISIBLE));
-
-        when(mBouncer.isAnimatingAway()).thenReturn(true);
-        mStatusBarKeyguardViewManager.onQsExpansionChanged(0f);
-        verify(mLockIconContainer).setVisibility(eq(View.INVISIBLE));
-    }
-
     private class TestableStatusBarKeyguardViewManager extends StatusBarKeyguardViewManager {
 
         public TestableStatusBarKeyguardViewManager(Context context,
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/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 1fa62ce..1220e82 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -760,7 +760,7 @@
                 if (predicate.test(alarm)) {
                     alarms.remove(i);
                     if (!reOrdering) {
-                        decrementAlarmCount(alarm.uid);
+                        decrementAlarmCount(alarm.uid, 1);
                     }
                     didRemove = true;
                     if (alarm.alarmClock != null) {
@@ -1764,7 +1764,7 @@
                                 + ", callingPackage: " + callingPackage;
                 // STOPSHIP (b/128866264): Just to catch breakages. Remove before final release.
                 Slog.wtf(TAG, errorMsg);
-                // TODO b/129995049: Resume throwing once issue is resolved.
+                // TODO b/129995049: Resume throwing after some soak time without errors
                 // throw new UnsupportedOperationException(errorMsg);
             }
             setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed,
@@ -3113,17 +3113,21 @@
             }
         }
         for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
-            if (mPendingWhileIdleAlarms.get(i).matches(operation, directReceiver)) {
+            final Alarm alarm = mPendingWhileIdleAlarms.get(i);
+            if (alarm.matches(operation, directReceiver)) {
                 // Don't set didRemove, since this doesn't impact the scheduled alarms.
                 mPendingWhileIdleAlarms.remove(i);
+                decrementAlarmCount(alarm.uid, 1);
             }
         }
         for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) {
             final ArrayList<Alarm> alarmsForUid = mPendingBackgroundAlarms.valueAt(i);
             for (int j = alarmsForUid.size() - 1; j >= 0; j--) {
-                if (alarmsForUid.get(j).matches(operation, directReceiver)) {
+                final Alarm alarm = alarmsForUid.get(j);
+                if (alarm.matches(operation, directReceiver)) {
                     // Don't set didRemove, since this doesn't impact the scheduled alarms.
                     alarmsForUid.remove(j);
+                    decrementAlarmCount(alarm.uid, 1);
                 }
             }
             if (alarmsForUid.size() == 0) {
@@ -3169,6 +3173,7 @@
             if (a.uid == uid) {
                 // Don't set didRemove, since this doesn't impact the scheduled alarms.
                 mPendingWhileIdleAlarms.remove(i);
+                decrementAlarmCount(uid, 1);
             }
         }
         for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i --) {
@@ -3176,6 +3181,7 @@
             for (int j = alarmsForUid.size() - 1; j >= 0; j--) {
                 if (alarmsForUid.get(j).uid == uid) {
                     alarmsForUid.remove(j);
+                    decrementAlarmCount(uid, 1);
                 }
             }
             if (alarmsForUid.size() == 0) {
@@ -3221,13 +3227,16 @@
             if (a.matches(packageName)) {
                 // Don't set didRemove, since this doesn't impact the scheduled alarms.
                 mPendingWhileIdleAlarms.remove(i);
+                decrementAlarmCount(a.uid, 1);
             }
         }
         for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i --) {
             final ArrayList<Alarm> alarmsForUid = mPendingBackgroundAlarms.valueAt(i);
             for (int j = alarmsForUid.size() - 1; j >= 0; j--) {
-                if (alarmsForUid.get(j).matches(packageName)) {
+                final Alarm alarm = alarmsForUid.get(j);
+                if (alarm.matches(packageName)) {
                     alarmsForUid.remove(j);
+                    decrementAlarmCount(alarm.uid, 1);
                 }
             }
             if (alarmsForUid.size() == 0) {
@@ -3272,10 +3281,15 @@
             if (a.uid == uid) {
                 // Don't set didRemove, since this doesn't impact the scheduled alarms.
                 mPendingWhileIdleAlarms.remove(i);
+                decrementAlarmCount(uid, 1);
             }
         }
         for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) {
             if (mPendingBackgroundAlarms.keyAt(i) == uid) {
+                final ArrayList<Alarm> toRemove = mPendingBackgroundAlarms.valueAt(i);
+                if (toRemove != null) {
+                    decrementAlarmCount(uid, toRemove.size());
+                }
                 mPendingBackgroundAlarms.removeAt(i);
             }
         }
@@ -3308,11 +3322,18 @@
             if (UserHandle.getUserId(mPendingWhileIdleAlarms.get(i).creatorUid)
                     == userHandle) {
                 // Don't set didRemove, since this doesn't impact the scheduled alarms.
-                mPendingWhileIdleAlarms.remove(i);
+                final Alarm removed = mPendingWhileIdleAlarms.remove(i);
+                decrementAlarmCount(removed.uid, 1);
             }
         }
         for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) {
             if (UserHandle.getUserId(mPendingBackgroundAlarms.keyAt(i)) == userHandle) {
+                final ArrayList<Alarm> toRemove = mPendingBackgroundAlarms.valueAt(i);
+                if (toRemove != null) {
+                    for (int j = 0; j < toRemove.size(); j++) {
+                        decrementAlarmCount(toRemove.get(j).uid, 1);
+                    }
+                }
                 mPendingBackgroundAlarms.removeAt(i);
             }
         }
@@ -3844,7 +3865,7 @@
                 Slog.w(TAG, "Failure sending alarm.", e);
             }
             Trace.traceEnd(Trace.TRACE_TAG_POWER);
-            decrementAlarmCount(alarm.uid);
+            decrementAlarmCount(alarm.uid, 1);
         }
     }
 
@@ -4198,7 +4219,7 @@
                                 removeImpl(alarm.operation, null);
                             }
                         }
-                        decrementAlarmCount(alarm.uid);
+                        decrementAlarmCount(alarm.uid, 1);
                     }
                     break;
                 }
@@ -4828,16 +4849,21 @@
         }
     }
 
-    private void decrementAlarmCount(int uid) {
+    private void decrementAlarmCount(int uid, int decrement) {
+        int oldCount = 0;
         final int uidIndex = mAlarmsPerUid.indexOfKey(uid);
         if (uidIndex >= 0) {
-            final int newCount = mAlarmsPerUid.valueAt(uidIndex) - 1;
-            if (newCount > 0) {
-                mAlarmsPerUid.setValueAt(uidIndex, newCount);
+            oldCount = mAlarmsPerUid.valueAt(uidIndex);
+            if (oldCount > decrement) {
+                mAlarmsPerUid.setValueAt(uidIndex, oldCount - decrement);
             } else {
                 mAlarmsPerUid.removeAt(uidIndex);
             }
         }
+        if (oldCount < decrement) {
+            Slog.wtf(TAG, "Attempt to decrement existing alarm count " + oldCount + " by "
+                    + decrement + " for uid " + uid);
+        }
     }
 
     private class ShellCmd extends ShellCommand {
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/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 19c3a71..64f4a35 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -81,6 +81,7 @@
     static final String[] sDeviceConfigScopes = new String[] {
         DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT,
+        DeviceConfig.NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS,
         DeviceConfig.NAMESPACE_MEDIA_NATIVE,
         DeviceConfig.NAMESPACE_NETD_NATIVE,
         DeviceConfig.NAMESPACE_RUNTIME_NATIVE,
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index b50af28..032af25 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -22,7 +22,6 @@
 
 import android.Manifest;
 import android.annotation.Nullable;
-import android.annotation.TestApi;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.attention.AttentionManagerInternal;
@@ -43,6 +42,9 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
@@ -56,7 +58,6 @@
 import android.util.SparseArray;
 import android.util.StatsLog;
 
-import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
@@ -76,15 +77,6 @@
     private static final String LOG_TAG = "AttentionManagerService";
     private static final boolean DEBUG = false;
 
-    /**
-     * DeviceConfig flag name, allows a CTS to inject a fake implementation.
-     *
-     * @hide
-     */
-    @TestApi
-    public static final String COMPONENT_NAME = "component_name";
-
-
     /** Default value in absence of {@link DeviceConfig} override. */
     private static final boolean DEFAULT_SERVICE_ENABLED = true;
 
@@ -96,6 +88,7 @@
 
     /** DeviceConfig flag name, if {@code true}, enables AttentionManagerService features. */
     private static final String SERVICE_ENABLED = "service_enabled";
+    private static String sTestAttentionServicePackage;
     private final Context mContext;
     private final PowerManager mPowerManager;
     private final Object mLock;
@@ -135,7 +128,7 @@
 
     /** Returns {@code true} if attention service is configured on this device. */
     public static boolean isServiceConfigured(Context context) {
-        return !TextUtils.isEmpty(getServiceConfig(context));
+        return !TextUtils.isEmpty(getServiceConfigPackage(context));
     }
 
     /** Resolves and sets up the attention service if it had not been done yet. */
@@ -332,8 +325,8 @@
         return mUserStates.get(userId);
     }
 
-    private static String getServiceConfig(Context context) {
-        return context.getString(R.string.config_defaultAttentionService);
+    private static String getServiceConfigPackage(Context context) {
+        return context.getPackageManager().getAttentionServicePackageName();
     }
 
     /**
@@ -341,28 +334,26 @@
      * system.
      */
     private static ComponentName resolveAttentionService(Context context) {
-        final String flag = DeviceConfig.getProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
-                COMPONENT_NAME);
+        final String serviceConfigPackage = getServiceConfigPackage(context);
 
-        final String componentNameString = flag != null ? flag : getServiceConfig(context);
-        if (TextUtils.isEmpty(componentNameString)) {
-            return null;
-        }
-
-        final ComponentName componentName = ComponentName.unflattenFromString(componentNameString);
-        if (componentName == null) {
+        String resolvedPackage;
+        int flags = PackageManager.MATCH_SYSTEM_ONLY;
+        if (!TextUtils.isEmpty(sTestAttentionServicePackage)) {
+            resolvedPackage = sTestAttentionServicePackage;
+            flags = PackageManager.GET_META_DATA;
+        } else if (!TextUtils.isEmpty(serviceConfigPackage)) {
+            resolvedPackage = serviceConfigPackage;
+        } else {
             return null;
         }
 
         final Intent intent = new Intent(AttentionService.SERVICE_INTERFACE).setPackage(
-                componentName.getPackageName());
+                resolvedPackage);
 
-        // Make sure that only system apps can declare the AttentionService.
-        final ResolveInfo resolveInfo = context.getPackageManager().resolveService(intent,
-                PackageManager.MATCH_SYSTEM_ONLY);
+        final ResolveInfo resolveInfo = context.getPackageManager().resolveService(intent, flags);
         if (resolveInfo == null || resolveInfo.serviceInfo == null) {
             Slog.wtf(LOG_TAG, String.format("Service %s not found in package %s",
-                    AttentionService.SERVICE_INTERFACE, componentName
+                    AttentionService.SERVICE_INTERFACE, serviceConfigPackage
             ));
             return null;
         }
@@ -447,6 +438,7 @@
         }
 
         void cancelInternal() {
+            mIsFulfilled = true;
             mCallbackInternal.onFailure(ATTENTION_FAILURE_CANCELLED);
         }
     }
@@ -606,7 +598,6 @@
             }
             return;
         }
-        userState.mCurrentAttentionCheck.mIsFulfilled = true;
 
         if (userState.mService == null) {
             userState.mCurrentAttentionCheck.cancelInternal();
@@ -654,7 +645,165 @@
         }
     }
 
+    private final class AttentionManagerServiceShellCommand extends ShellCommand {
+        class TestableAttentionCallbackInternal extends AttentionCallbackInternal {
+            private int mLastCallbackCode = -1;
+
+            @Override
+            public void onSuccess(int result, long timestamp) {
+                mLastCallbackCode = result;
+            }
+
+            @Override
+            public void onFailure(int error) {
+                mLastCallbackCode = error;
+            }
+
+            public void reset() {
+                mLastCallbackCode = -1;
+            }
+
+            public int getLastCallbackCode() {
+                return mLastCallbackCode;
+            }
+        }
+
+        final TestableAttentionCallbackInternal mTestableAttentionCallback =
+                new TestableAttentionCallbackInternal();
+
+        @Override
+        public int onCommand(@Nullable final String cmd) {
+            if (cmd == null) {
+                return handleDefaultCommands(cmd);
+            }
+            final PrintWriter err = getErrPrintWriter();
+            try {
+                switch (cmd) {
+                    case "getAttentionServiceComponent":
+                        return cmdResolveAttentionServiceComponent();
+                    case "call":
+                        switch (getNextArgRequired()) {
+                            case "checkAttention":
+                                return cmdCallCheckAttention();
+                            case "cancelCheckAttention":
+                                return cmdCallCancelAttention();
+                            default:
+                                throw new IllegalArgumentException("Invalid argument");
+                        }
+                    case "setTestableAttentionService":
+                        return cmdSetTestableAttentionService(getNextArgRequired());
+                    case "clearTestableAttentionService":
+                        return cmdClearTestableAttentionService();
+                    case "getLastTestCallbackCode":
+                        return cmdGetLastTestCallbackCode();
+                    default:
+                        return handleDefaultCommands(cmd);
+                }
+            } catch (IllegalArgumentException e) {
+                err.println("Error: " + e.getMessage());
+            }
+            return -1;
+        }
+
+        private int cmdSetTestableAttentionService(String testingServicePackage) {
+            final PrintWriter out = getOutPrintWriter();
+            if (TextUtils.isEmpty(testingServicePackage)) {
+                out.println("false");
+            } else {
+                sTestAttentionServicePackage = testingServicePackage;
+                resetStates();
+                out.println(mComponentName != null ? "true" : "false");
+            }
+            return 0;
+        }
+
+        private int cmdClearTestableAttentionService() {
+            sTestAttentionServicePackage = "";
+            mTestableAttentionCallback.reset();
+            resetStates();
+            return 0;
+        }
+
+        private int cmdCallCheckAttention() {
+            final PrintWriter out = getOutPrintWriter();
+            boolean calledSuccessfully = checkAttention(2000, mTestableAttentionCallback);
+            out.println(calledSuccessfully ? "true" : "false");
+            return 0;
+        }
+
+        private int cmdCallCancelAttention() {
+            final PrintWriter out = getOutPrintWriter();
+            cancelAttentionCheck(mTestableAttentionCallback);
+            out.println("true");
+            return 0;
+        }
+
+        private int cmdResolveAttentionServiceComponent() {
+            final PrintWriter out = getOutPrintWriter();
+            ComponentName resolvedComponent = resolveAttentionService(mContext);
+            out.println(resolvedComponent != null ? resolvedComponent.flattenToShortString() : "");
+            return 0;
+        }
+
+        private int cmdGetLastTestCallbackCode() {
+            final PrintWriter out = getOutPrintWriter();
+            out.println(mTestableAttentionCallback.getLastCallbackCode());
+            return 0;
+        }
+
+        private void resetStates() {
+            mComponentName = resolveAttentionService(mContext);
+            mUserStates.clear();
+        }
+
+        @Override
+        public void onHelp() {
+            final PrintWriter out = getOutPrintWriter();
+            out.println("Attention commands: ");
+            out.println("  setTestableAttentionService <service_package>: Bind to a custom"
+                    + " implementation of attention service");
+            out.println("  ---<service_package>:");
+            out.println(
+                    "       := Package containing the Attention Service implementation to bind to");
+            out.println("  ---returns:");
+            out.println("       := true, if was bound successfully");
+            out.println("       := false, if was not bound successfully");
+            out.println("  clearTestableAttentionService: Undo custom bindings. Revert to previous"
+                    + " behavior");
+            out.println("  getAttentionServiceComponent: Get the current service component string");
+            out.println("  ---returns:");
+            out.println("       := If valid, the component string (in shorten form) for the"
+                    + " currently bound service.");
+            out.println("       := else, empty string");
+            out.println("  call checkAttention: Calls check attention");
+            out.println("  ---returns:");
+            out.println(
+                    "       := true, if the call was successfully dispatched to the service "
+                            + "implementation."
+                            + " (to see the result, call getLastTestCallbackCode)");
+            out.println("       := false, otherwise");
+            out.println("  call cancelCheckAttention: Cancels check attention");
+            out.println("  getLastTestCallbackCode");
+            out.println("  ---returns:");
+            out.println(
+                    "       := An integer, representing the last callback code received from the "
+                            + "bounded implementation. If none, it will return -1");
+        }
+    }
+
     private final class BinderService extends Binder {
+        AttentionManagerServiceShellCommand mAttentionManagerServiceShellCommand =
+                new AttentionManagerServiceShellCommand();
+
+        @Override
+        public void onShellCommand(FileDescriptor in, FileDescriptor out,
+                FileDescriptor err,
+                String[] args, ShellCallback callback,
+                ResultReceiver resultReceiver) {
+            mAttentionManagerServiceShellCommand.exec(this, in, out, err, args, callback,
+                    resultReceiver);
+        }
+
         @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 668af85..a0522e3 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -723,7 +723,8 @@
                 case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
                     synchronized (mDeviceStateLock) {
                         mDeviceInventory.onSetHearingAidConnectionState(
-                                (BluetoothDevice) msg.obj, msg.arg1);
+                                (BluetoothDevice) msg.obj, msg.arg1,
+                                mAudioService.getHearingAidStreamType());
                     }
                     break;
                 case MSG_BT_HEADSET_CNCT_FAILED:
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 3948bd8..887c908 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -236,7 +236,7 @@
     }
 
     /*package*/ void onSetHearingAidConnectionState(BluetoothDevice btDevice,
-                @AudioService.BtProfileConnectionState int state) {
+                @AudioService.BtProfileConnectionState int state, int streamType) {
         String address = btDevice.getAddress();
         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
             address = "";
@@ -253,7 +253,7 @@
             if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
                 makeHearingAidDeviceUnavailable(address);
             } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
-                makeHearingAidDeviceAvailable(address, BtHelper.getName(btDevice),
+                makeHearingAidDeviceAvailable(address, BtHelper.getName(btDevice), streamType,
                         "onSetHearingAidConnectionState");
             }
         }
@@ -719,10 +719,11 @@
     }
 
     @GuardedBy("mConnectedDevices")
-    private void makeHearingAidDeviceAvailable(String address, String name, String eventSource) {
-        final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(AudioSystem.STREAM_MUSIC,
+    private void makeHearingAidDeviceAvailable(
+            String address, String name, int streamType, String eventSource) {
+        final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType,
                 AudioSystem.DEVICE_OUT_HEARING_AID);
-        mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, AudioSystem.STREAM_MUSIC);
+        mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType);
 
         AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
                 AudioSystem.DEVICE_STATE_AVAILABLE, address, name,
@@ -732,7 +733,7 @@
                 new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name,
                         address, AudioSystem.AUDIO_FORMAT_DEFAULT));
         mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID);
-        mDeviceBroker.postApplyVolumeOnDevice(AudioSystem.STREAM_MUSIC,
+        mDeviceBroker.postApplyVolumeOnDevice(streamType,
                 AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
         setCurrentAudioRouteNameIfPossible(name);
     }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c34425f..507f398 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -2078,6 +2078,22 @@
         }
     }
 
+    /*package*/ int getHearingAidStreamType() {
+        return getHearingAidStreamType(mMode);
+    }
+
+    private int getHearingAidStreamType(int mode) {
+        switch (mode) {
+            case AudioSystem.MODE_IN_COMMUNICATION:
+            case AudioSystem.MODE_IN_CALL:
+                return AudioSystem.STREAM_VOICE_CALL;
+            case AudioSystem.MODE_NORMAL:
+            default:
+                break;
+        }
+        return AudioSystem.STREAM_MUSIC;
+    }
+
     /**
      * Manage an audio mode change for audio devices that use an "absolute volume" model,
      * i.e. the framework sends the full scale signal, and the actual volume for the use case
@@ -2087,14 +2103,10 @@
         if (oldMode == newMode) {
             return;
         }
-        int streamType = AudioSystem.STREAM_MUSIC;
         switch (newMode) {
             case AudioSystem.MODE_IN_COMMUNICATION:
             case AudioSystem.MODE_IN_CALL:
-                streamType = AudioSystem.STREAM_VOICE_CALL;
-                break;
             case AudioSystem.MODE_NORMAL:
-                streamType = AudioSystem.STREAM_MUSIC;
                 break;
             case AudioSystem.MODE_RINGTONE:
                 // not changing anything for ringtone
@@ -2105,6 +2117,9 @@
                 // don't know what to do in this case, better bail
                 return;
         }
+
+        int streamType = getHearingAidStreamType(newMode);
+
         final int device = AudioSystem.getDevicesForStream(streamType);
         if ((device & mAbsVolumeMultiModeCaseDevices) == 0) {
             return;
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 c6f6c50..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/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 981e0f5..f81015d 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -80,7 +80,6 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 
@@ -731,12 +730,14 @@
 
     public void writeXml(XmlSerializer out, boolean forBackup, Integer version, int userId)
             throws IOException {
-        final int N = mConfigs.size();
-        for (int i = 0; i < N; i++) {
-            if (forBackup && mConfigs.keyAt(i) != userId) {
-                continue;
+        synchronized (mConfigs) {
+            final int n = mConfigs.size();
+            for (int i = 0; i < n; i++) {
+                if (forBackup && mConfigs.keyAt(i) != userId) {
+                    continue;
+                }
+                mConfigs.valueAt(i).writeXml(out, version);
             }
-            mConfigs.valueAt(i).writeXml(out, version);
         }
     }
 
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 21b6f12..497385f 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -22,21 +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;
@@ -45,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.
@@ -122,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;
     }
 
     /**
@@ -237,11 +283,7 @@
      * @return true if APEX packages can be managed on this device, false otherwise.
      */
     boolean isApexSupported() {
-        populateActivePackagesCacheIfNeeded();
-        // There is no system-wide property available to check if APEX are flattened and hence can't
-        // be updated. In absence of such property, we assume that if we didn't index APEX packages
-        // since they were flattened, no APEX management should be possible.
-        return !mActivePackagesCache.isEmpty();
+        return ApexProperties.updatable().orElse(false);
     }
 
     /**
@@ -277,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.
@@ -289,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;
@@ -334,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/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index ead09b4..e859893 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -185,16 +185,18 @@
         proto.write(PackageProto.NAME, (realName != null ? realName : name));
         proto.write(PackageProto.UID, appId);
         proto.write(PackageProto.VERSION_CODE, versionCode);
-        proto.write(PackageProto.VERSION_STRING, pkg.mVersionName);
         proto.write(PackageProto.INSTALL_TIME_MS, firstInstallTime);
         proto.write(PackageProto.UPDATE_TIME_MS, lastUpdateTime);
         proto.write(PackageProto.INSTALLER_NAME, installerPackageName);
 
         if (pkg != null) {
+            proto.write(PackageProto.VERSION_STRING, pkg.mVersionName);
+
             long splitToken = proto.start(PackageProto.SPLITS);
             proto.write(PackageProto.SplitProto.NAME, "base");
             proto.write(PackageProto.SplitProto.REVISION_CODE, pkg.baseRevisionCode);
             proto.end(splitToken);
+
             if (pkg.splitNames != null) {
                 for (int i = 0; i < pkg.splitNames.length; i++) {
                     splitToken = proto.start(PackageProto.SPLITS);
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 6154726..db2c742 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -70,6 +70,7 @@
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -588,6 +589,7 @@
             // rollback sessions been applied.
             List<RollbackData> enabling = new ArrayList<>();
             List<RollbackData> restoreInProgress = new ArrayList<>();
+            Set<String> apexPackageNames = new HashSet<>();
             synchronized (mLock) {
                 ensureRollbackDataLoadedLocked();
                 for (RollbackData data : mRollbacks) {
@@ -597,6 +599,12 @@
                         } else if (data.restoreUserDataInProgress) {
                             restoreInProgress.add(data);
                         }
+
+                        for (PackageRollbackInfo info : data.info.getPackages()) {
+                            if (info.isApex()) {
+                                apexPackageNames.add(info.getPackageName());
+                            }
+                        }
                     }
                 }
             }
@@ -634,6 +642,14 @@
                 }
             }
 
+            for (String apexPackageName : apexPackageNames) {
+                // We will not recieve notifications when an apex is updated,
+                // so check now in case any rollbacks ought to be expired. The
+                // onPackagedReplace function is safe to call if the package
+                // hasn't actually been updated.
+                onPackageReplaced(apexPackageName);
+            }
+
             mPackageHealthObserver.onBootCompleted();
         });
     }
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 4ed07c3..8a834c8 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -5438,7 +5438,7 @@
             if (isAttached()) {
                 getDisplay().positionChildAtBottom(this);
             }
-            if (!isActivityTypeHome() || getDisplay().isRemoved()) {
+            if (!isActivityTypeHome() || !isAttached()) {
                 remove();
             }
         }
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/net/java/android/net/ip/IIpClient.aidl b/services/net/java/android/net/ip/IIpClient.aidl
index 1e77264..9989c52 100644
--- a/services/net/java/android/net/ip/IIpClient.aidl
+++ b/services/net/java/android/net/ip/IIpClient.aidl
@@ -17,6 +17,7 @@
 
 import android.net.ProxyInfo;
 import android.net.ProvisioningConfigurationParcelable;
+import android.net.NattKeepalivePacketDataParcelable;
 import android.net.TcpKeepalivePacketDataParcelable;
 
 /** @hide */
@@ -33,4 +34,5 @@
     void addKeepalivePacketFilter(int slot, in TcpKeepalivePacketDataParcelable pkt);
     void removeKeepalivePacketFilter(int slot);
     void setL2KeyAndGroupHint(in String l2Key, in String groupHint);
+    void addNattKeepalivePacketFilter(int slot, in NattKeepalivePacketDataParcelable pkt);
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
index c0a11b2..6517303 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
@@ -263,6 +263,9 @@
 
         mService.onStart();
         spyOn(mService.mHandler);
+        // Stubbing the handler. Test should simulate any handling of messages synchronously.
+        doReturn(true).when(mService.mHandler).sendMessageAtTime(any(Message.class), anyLong());
+
         assertEquals(mService.mSystemUiUid, SYSTEM_UI_UID);
         assertEquals(mService.mClockReceiver, mClockReceiver);
         assertEquals(mService.mWakeLock, mWakeLock);
@@ -617,11 +620,9 @@
         testQuotasNoDeferral(STANDBY_BUCKET_RARE);
     }
 
-    private void sendAndHandleBucketChanged(int bucket) {
+    private void assertAndHandleBucketChanged(int bucket) {
         when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
                 anyLong())).thenReturn(bucket);
-        // Stubbing the handler call to simulate it synchronously here.
-        doReturn(true).when(mService.mHandler).sendMessage(any(Message.class));
         mAppStandbyListener.onAppIdleStateChanged(TEST_CALLING_PACKAGE,
                 UserHandle.getUserId(TEST_CALLING_UID), false, bucket, 0);
         final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
@@ -652,7 +653,7 @@
         // The next upcoming alarm in queue should also be set as expected.
         assertEquals(firstTrigger + workingQuota - 1, mTestTimer.getElapsed());
         // Downgrading the bucket now
-        sendAndHandleBucketChanged(STANDBY_BUCKET_RARE);
+        assertAndHandleBucketChanged(STANDBY_BUCKET_RARE);
         final int rareQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_RARE);
         // The last alarm should now be deferred.
         final long expectedNextTrigger = (firstTrigger + workingQuota - 1 - rareQuota)
@@ -680,15 +681,13 @@
         assertEquals(deferredTrigger, mTestTimer.getElapsed());
 
         // Upgrading the bucket now
-        sendAndHandleBucketChanged(STANDBY_BUCKET_ACTIVE);
+        assertAndHandleBucketChanged(STANDBY_BUCKET_ACTIVE);
         // The last alarm should now be rescheduled to go as per original expectations
         final long originalTrigger = firstTrigger + frequentQuota;
         assertEquals("Incorrect next alarm trigger", originalTrigger, mTestTimer.getElapsed());
     }
 
-    private void sendAndHandleParoleChanged(boolean parole) {
-        // Stubbing the handler call to simulate it synchronously here.
-        doReturn(true).when(mService.mHandler).sendMessage(any(Message.class));
+    private void assertAndHandleParoleChanged(boolean parole) {
         mAppStandbyListener.onParoleStateChanged(parole);
         final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
         verify(mService.mHandler, atLeastOnce()).sendMessage(messageCaptor.capture());
@@ -719,7 +718,7 @@
         // Any subsequent alarms in queue should all be deferred
         assertEquals(firstTrigger + mAppStandbyWindow + 1, mTestTimer.getElapsed());
         // Paroling now
-        sendAndHandleParoleChanged(true);
+        assertAndHandleParoleChanged(true);
 
         // Subsequent alarms should now go off as per original expectations.
         for (int i = 0; i < 5; i++) {
@@ -728,7 +727,7 @@
             mTestTimer.expire();
         }
         // Come out of parole
-        sendAndHandleParoleChanged(false);
+        assertAndHandleParoleChanged(false);
 
         // Subsequent alarms should again get deferred
         final long expectedNextTrigger = (firstTrigger + 5) + 1 + mAppStandbyWindow;
@@ -938,6 +937,26 @@
     }
 
     @Test
+    public void alarmCountOnRemoveFromPendingWhileIdle() {
+        mService.mPendingIdleUntil = mock(AlarmManagerService.Alarm.class);
+        final int numAlarms = 15;
+        final PendingIntent[] pis = new PendingIntent[numAlarms];
+        for (int i = 0; i < numAlarms; i++) {
+            pis[i] = getNewMockPendingIntent();
+            setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 5, pis[i]);
+        }
+        assertEquals(numAlarms, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
+        assertEquals(numAlarms, mService.mPendingWhileIdleAlarms.size());
+        final int toRemove = 8;
+        for (int i = 0; i < toRemove; i++) {
+            mService.removeLocked(pis[i], null);
+            assertEquals(numAlarms - i - 1, mService.mAlarmsPerUid.get(TEST_CALLING_UID, 0));
+        }
+        mService.removeLocked(TEST_CALLING_UID);
+        assertEquals(0, mService.mAlarmsPerUid.get(TEST_CALLING_UID, 0));
+    }
+
+    @Test
     public void alarmCountOnAlarmRemoved() {
         final int numAlarms = 10;
         final PendingIntent[] pis = new PendingIntent[numAlarms];
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/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerLogger.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerLogger.java
new file mode 100644
index 0000000..73b4ce7
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerLogger.java
@@ -0,0 +1,136 @@
+/*
+ * 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.soundtrigger;
+
+import android.util.Log;
+
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.LinkedList;
+
+/**
+* Constructor SoundTriggerLogger class
+*/
+public class SoundTriggerLogger {
+
+    // ring buffer of events to log.
+    private final LinkedList<Event> mEvents;
+
+    private final String mTitle;
+
+    // the maximum number of events to keep in log
+    private final int mMemSize;
+
+    /**
+     * Constructor for Event class.
+     */
+    public abstract static class Event {
+        // formatter for timestamps
+        private static final SimpleDateFormat sFormat = new SimpleDateFormat("MM-dd HH:mm:ss:SSS");
+
+        private final long mTimestamp;
+
+        Event() {
+            mTimestamp = System.currentTimeMillis();
+        }
+
+    /**
+     * Convert event to String
+     * @return StringBuilder
+     */
+        public String toString() {
+            return (new StringBuilder(sFormat.format(new Date(mTimestamp))))
+                    .append(" ").append(eventToString()).toString();
+        }
+
+        /**
+         * Causes the string message for the event to appear in the logcat.
+         * Here is an example of how to create a new event (a StringEvent), adding it to the logger
+         * (an instance of SoundTriggerLogger) while also making it show in the logcat:
+         * <pre>
+         *     myLogger.log(
+         *         (new StringEvent("something for logcat and logger")).printLog(MyClass.TAG) );
+         * </pre>
+         * @param tag the tag for the android.util.Log.v
+         * @return the same instance of the event
+         */
+        public Event printLog(String tag) {
+            Log.i(tag, eventToString());
+            return this;
+        }
+
+        /**
+         * Convert event to String.
+         * This method is only called when the logger history is about to the dumped,
+         * so this method is where expensive String conversions should be made, not when the Event
+         * subclass is created.
+         * Timestamp information will be automatically added, do not include it.
+         * @return a string representation of the event that occurred.
+         */
+        public abstract String eventToString();
+    }
+
+    /**
+    * Constructor StringEvent class
+    */
+    public static class StringEvent extends Event {
+        private final String mMsg;
+
+        public StringEvent(String msg) {
+            mMsg = msg;
+        }
+
+        @Override
+        public String eventToString() {
+            return mMsg;
+        }
+    }
+
+    /**
+     * Constructor for logger.
+     * @param size the maximum number of events to keep in log
+     * @param title the string displayed before the recorded log
+     */
+    public SoundTriggerLogger(int size, String title) {
+        mEvents = new LinkedList<Event>();
+        mMemSize = size;
+        mTitle = title;
+    }
+
+    /**
+     * Constructor for logger.
+     * @param evt the maximum number of events to keep in log
+     */
+    public synchronized void log(Event evt) {
+        if (mEvents.size() >= mMemSize) {
+            mEvents.removeFirst();
+        }
+        mEvents.add(evt);
+    }
+
+    /**
+     * Constructor for logger.
+     * @param pw the maximum number of events to keep in log
+     */
+    public synchronized void dump(PrintWriter pw) {
+        pw.println("ST Event log: " + mTitle);
+        for (Event evt : mEvents) {
+            pw.println(evt.toString());
+        }
+    }
+}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 697469a..9c4c099 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -180,9 +180,16 @@
                 Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid);
             }
 
+            sEventLogger.log(new SoundTriggerLogger.StringEvent("startRecognition(): Uuid : "
+                    + parcelUuid));
+
             GenericSoundModel model = getSoundModel(parcelUuid);
             if (model == null) {
                 Slog.e(TAG, "Null model in database for id: " + parcelUuid);
+
+                sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        "startRecognition(): Null model in database for id: " + parcelUuid));
+
                 return STATUS_ERROR;
             }
 
@@ -196,6 +203,10 @@
             if (DEBUG) {
                 Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid);
             }
+
+            sEventLogger.log(new SoundTriggerLogger.StringEvent("stopRecognition(): Uuid : "
+                    + parcelUuid));
+
             if (!isInitialized()) return STATUS_ERROR;
             return mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(), callback);
         }
@@ -206,6 +217,10 @@
             if (DEBUG) {
                 Slog.i(TAG, "getSoundModel(): id = " + soundModelId);
             }
+
+            sEventLogger.log(new SoundTriggerLogger.StringEvent("getSoundModel(): id = "
+                    + soundModelId));
+
             SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel(
                     soundModelId.getUuid());
             return model;
@@ -217,6 +232,10 @@
             if (DEBUG) {
                 Slog.i(TAG, "updateSoundModel(): model = " + soundModel);
             }
+
+            sEventLogger.log(new SoundTriggerLogger.StringEvent("updateSoundModel(): model = "
+                    + soundModel));
+
             mDbHelper.updateGenericSoundModel(soundModel);
         }
 
@@ -226,6 +245,10 @@
             if (DEBUG) {
                 Slog.i(TAG, "deleteSoundModel(): id = " + soundModelId);
             }
+
+            sEventLogger.log(new SoundTriggerLogger.StringEvent("deleteSoundModel(): id = "
+                    + soundModelId));
+
             // Unload the model if it is loaded.
             mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid());
             mDbHelper.deleteGenericSoundModel(soundModelId.getUuid());
@@ -237,11 +260,19 @@
             if (!isInitialized()) return STATUS_ERROR;
             if (soundModel == null || soundModel.uuid == null) {
                 Slog.e(TAG, "Invalid sound model");
+
+                sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        "loadGenericSoundModel(): Invalid sound model"));
+
                 return STATUS_ERROR;
             }
             if (DEBUG) {
                 Slog.i(TAG, "loadGenericSoundModel(): id = " + soundModel.uuid);
             }
+
+            sEventLogger.log(new SoundTriggerLogger.StringEvent("loadGenericSoundModel(): id = "
+                    + soundModel.uuid));
+
             synchronized (mLock) {
                 SoundModel oldModel = mLoadedModels.get(soundModel.uuid);
                 // If the model we're loading is actually different than what we had loaded, we
@@ -264,15 +295,28 @@
             if (!isInitialized()) return STATUS_ERROR;
             if (soundModel == null || soundModel.uuid == null) {
                 Slog.e(TAG, "Invalid sound model");
+
+                sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        "loadKeyphraseSoundModel(): Invalid sound model"));
+
                 return STATUS_ERROR;
             }
             if (soundModel.keyphrases == null || soundModel.keyphrases.length != 1) {
                 Slog.e(TAG, "Only one keyphrase per model is currently supported.");
+
+                sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        "loadKeyphraseSoundModel(): Only one keyphrase per model"
+                        + " is currently supported."));
+
                 return STATUS_ERROR;
             }
             if (DEBUG) {
                 Slog.i(TAG, "loadKeyphraseSoundModel(): id = " + soundModel.uuid);
             }
+
+            sEventLogger.log(new SoundTriggerLogger.StringEvent("loadKeyphraseSoundModel(): id = "
+                    + soundModel.uuid));
+
             synchronized (mLock) {
                 SoundModel oldModel = mLoadedModels.get(soundModel.uuid);
                 // If the model we're loading is actually different than what we had loaded, we
@@ -303,6 +347,9 @@
                 Slog.i(TAG, "startRecognition(): id = " + soundModelId);
             }
 
+            sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                    "startRecognitionForService(): id = " + soundModelId));
+
             IRecognitionStatusCallback callback =
                     new RemoteSoundTriggerDetectionService(soundModelId.getUuid(), params,
                             detectionService, Binder.getCallingUserHandle(), config);
@@ -311,6 +358,10 @@
                 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
                 if (soundModel == null) {
                     Slog.e(TAG, soundModelId + " is not loaded");
+
+                    sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                            "startRecognitionForService():" + soundModelId + " is not loaded"));
+
                     return STATUS_ERROR;
                 }
                 IRecognitionStatusCallback existingCallback = null;
@@ -319,6 +370,11 @@
                 }
                 if (existingCallback != null) {
                     Slog.e(TAG, soundModelId + " is already running");
+
+                    sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                            "startRecognitionForService():"
+                            + soundModelId + " is already running"));
+
                     return STATUS_ERROR;
                 }
                 int ret;
@@ -329,11 +385,19 @@
                         break;
                     default:
                         Slog.e(TAG, "Unknown model type");
+
+                        sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                                "startRecognitionForService(): Unknown model type"));
+
                         return STATUS_ERROR;
                 }
 
                 if (ret != STATUS_OK) {
                     Slog.e(TAG, "Failed to start model: " + ret);
+
+                    sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                            "startRecognitionForService(): Failed to start model:"));
+
                     return ret;
                 }
                 synchronized (mCallbacksLock) {
@@ -351,10 +415,18 @@
                 Slog.i(TAG, "stopRecognition(): id = " + soundModelId);
             }
 
+            sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                    "stopRecognitionForService(): id = " + soundModelId));
+
             synchronized (mLock) {
                 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
                 if (soundModel == null) {
                     Slog.e(TAG, soundModelId + " is not loaded");
+
+                    sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                            "stopRecognitionForService(): " + soundModelId
+                            + " is not loaded"));
+
                     return STATUS_ERROR;
                 }
                 IRecognitionStatusCallback callback = null;
@@ -363,6 +435,11 @@
                 }
                 if (callback == null) {
                     Slog.e(TAG, soundModelId + " is not running");
+
+                    sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                            "stopRecognitionForService(): " + soundModelId
+                            + " is not running"));
+
                     return STATUS_ERROR;
                 }
                 int ret;
@@ -372,11 +449,19 @@
                         break;
                     default:
                         Slog.e(TAG, "Unknown model type");
+
+                        sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                                "stopRecognitionForService(): Unknown model type"));
+
                         return STATUS_ERROR;
                 }
 
                 if (ret != STATUS_OK) {
                     Slog.e(TAG, "Failed to stop model: " + ret);
+
+                    sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                                "stopRecognitionForService(): Failed to stop model: " + ret));
+
                     return ret;
                 }
                 synchronized (mCallbacksLock) {
@@ -394,10 +479,17 @@
                 Slog.i(TAG, "unloadSoundModel(): id = " + soundModelId);
             }
 
+            sEventLogger.log(new SoundTriggerLogger.StringEvent("unloadSoundModel(): id = "
+                    + soundModelId));
+
             synchronized (mLock) {
                 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
                 if (soundModel == null) {
                     Slog.e(TAG, soundModelId + " is not loaded");
+
+                    sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                                "unloadSoundModel(): " + soundModelId + " is not loaded"));
+
                     return STATUS_ERROR;
                 }
                 int ret;
@@ -411,10 +503,18 @@
                         break;
                     default:
                         Slog.e(TAG, "Unknown model type");
+
+                        sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                                "unloadSoundModel(): Unknown model type"));
+
                         return STATUS_ERROR;
                 }
                 if (ret != STATUS_OK) {
                     Slog.e(TAG, "Failed to unload model");
+
+                    sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                            "unloadSoundModel(): Failed to unload model"));
+
                     return ret;
                 }
                 mLoadedModels.remove(soundModelId.getUuid());
@@ -444,10 +544,17 @@
                 Slog.i(TAG, "getModelState(): id = " + soundModelId);
             }
 
+            sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): id = "
+                    + soundModelId));
+
             synchronized (mLock) {
                 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
                 if (soundModel == null) {
                     Slog.e(TAG, soundModelId + " is not loaded");
+
+                    sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): "
+                            + soundModelId + " is not loaded"));
+
                     return ret;
                 }
                 switch (soundModel.type) {
@@ -459,6 +566,10 @@
                         break;
                     default:
                         Slog.e(TAG, "Unknown model type");
+
+                        sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                                "getModelState(): Unknown model type"));
+
                         break;
                 }
 
@@ -708,6 +819,10 @@
                     mService.removeClient(mPuuid);
                 } catch (Exception e) {
                     Slog.e(TAG, mPuuid + ": Cannot remove client", e);
+
+                    sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                            + ": Cannot remove client"));
+
                 }
 
                 mService = null;
@@ -730,6 +845,8 @@
         private void destroy() {
             if (DEBUG) Slog.v(TAG, mPuuid + ": destroy");
 
+            sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + ": destroy"));
+
             synchronized (mRemoteServiceLock) {
                 disconnectLocked();
 
@@ -761,6 +878,10 @@
                         } catch (Exception e) {
                             Slog.e(TAG, mPuuid + ": Could not stop operation "
                                     + mRunningOpIds.valueAt(i), e);
+
+                            sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                                    + ": Could not stop operation " + mRunningOpIds.valueAt(i)));
+
                         }
                     }
 
@@ -786,6 +907,10 @@
 
                 if (ri == null) {
                     Slog.w(TAG, mPuuid + ": " + mServiceName + " not found");
+
+                    sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                            + ": " + mServiceName + " not found"));
+
                     return;
                 }
 
@@ -793,6 +918,11 @@
                         .equals(ri.serviceInfo.permission)) {
                     Slog.w(TAG, mPuuid + ": " + mServiceName + " does not require "
                             + BIND_SOUND_TRIGGER_DETECTION_SERVICE);
+
+                    sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                            + ": " + mServiceName + " does not require "
+                            + BIND_SOUND_TRIGGER_DETECTION_SERVICE));
+
                     return;
                 }
 
@@ -803,6 +933,10 @@
                     mRemoteServiceWakeLock.acquire();
                 } else {
                     Slog.w(TAG, mPuuid + ": Could not bind to " + mServiceName);
+
+                    sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                            + ": Could not bind to " + mServiceName));
+
                 }
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -821,6 +955,9 @@
                     Slog.w(TAG, mPuuid + ": Dropped operation as already destroyed or marked for "
                             + "destruction");
 
+                    sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                            + ":Dropped operation as already destroyed or marked for destruction"));
+
                     op.drop();
                     return;
                 }
@@ -847,11 +984,20 @@
                             if (DEBUG || opsAllowed + 10 > opsAdded) {
                                 Slog.w(TAG, mPuuid + ": Dropped operation as too many operations "
                                         + "were run in last 24 hours");
+
+                                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                                        + ": Dropped operation as too many operations "
+                                        + "were run in last 24 hours"));
+
                             }
 
                             op.drop();
                         } catch (Exception e) {
                             Slog.e(TAG, mPuuid + ": Could not drop operation", e);
+
+                            sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                                        + ": Could not drop operation"));
+
                         }
                     } else {
                         mNumOps.addOp(currentTime);
@@ -866,10 +1012,17 @@
                         try {
                             if (DEBUG) Slog.v(TAG, mPuuid + ": runOp " + opId);
 
+                            sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                                        + ": runOp " + opId));
+
                             op.run(opId, mService);
                             mRunningOpIds.add(opId);
                         } catch (Exception e) {
                             Slog.e(TAG, mPuuid + ": Could not run operation " + opId, e);
+
+                            sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                                        + ": Could not run operation " + opId));
+
                         }
                     }
 
@@ -897,6 +1050,10 @@
         public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) {
             Slog.w(TAG, mPuuid + "->" + mServiceName + ": IGNORED onKeyphraseDetected(" + event
                     + ")");
+
+            sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + "->" + mServiceName
+                    + ": IGNORED onKeyphraseDetected(" + event + ")"));
+
         }
 
         /**
@@ -928,6 +1085,8 @@
                             : AudioFormat.CHANNEL_IN_MONO,
                     captureFormat.getEncoding());
 
+            sEventLogger.log(new SoundTriggerLogger.StringEvent("createAudioRecordForEvent"));
+
             return new AudioRecord(attributes, captureFormat, bufferSize,
                     event.getCaptureSession());
         }
@@ -936,6 +1095,9 @@
         public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
             if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event);
 
+            sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                    + ": Generic sound trigger event: " + event));
+
             runOrAddOperation(new Operation(
                     // always execute:
                     () -> {
@@ -966,6 +1128,9 @@
         public void onError(int status) {
             if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
 
+            sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                    + ": onError: " + status));
+
             runOrAddOperation(
                     new Operation(
                             // always execute:
@@ -985,17 +1150,28 @@
         @Override
         public void onRecognitionPaused() {
             Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused");
+
+            sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                    + "->" + mServiceName + ": IGNORED onRecognitionPaused"));
+
         }
 
         @Override
         public void onRecognitionResumed() {
             Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionResumed");
+
+            sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                    + "->" + mServiceName + ": IGNORED onRecognitionResumed"));
+
         }
 
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
             if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceConnected(" + service + ")");
 
+            sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                    + ": onServiceConnected(" + service + ")"));
+
             synchronized (mRemoteServiceLock) {
                 mService = ISoundTriggerDetectionService.Stub.asInterface(service);
 
@@ -1016,6 +1192,9 @@
         public void onServiceDisconnected(ComponentName name) {
             if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceDisconnected");
 
+            sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                    + ": onServiceDisconnected"));
+
             synchronized (mRemoteServiceLock) {
                 mService = null;
             }
@@ -1025,6 +1204,9 @@
         public void onBindingDied(ComponentName name) {
             if (DEBUG) Slog.v(TAG, mPuuid + ": onBindingDied");
 
+            sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                    + ": onBindingDied"));
+
             synchronized (mRemoteServiceLock) {
                 destroy();
             }
@@ -1034,6 +1216,9 @@
         public void onNullBinding(ComponentName name) {
             Slog.w(TAG, name + " for model " + mPuuid + " returned a null binding");
 
+            sEventLogger.log(new SoundTriggerLogger.StringEvent(name + " for model "
+                    + mPuuid + " returned a null binding"));
+
             synchronized (mRemoteServiceLock) {
                 disconnectLocked();
             }
@@ -1082,11 +1267,17 @@
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!isInitialized()) return;
             mSoundTriggerHelper.dump(fd, pw, args);
+            // log
+            sEventLogger.dump(pw);
         }
 
         private synchronized boolean isInitialized() {
             if (mSoundTriggerHelper == null ) {
                 Slog.e(TAG, "SoundTriggerHelper not initialized.");
+
+                sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        "SoundTriggerHelper not initialized."));
+
                 return false;
             }
             return true;
@@ -1099,4 +1290,11 @@
             throw new SecurityException("Caller does not hold the permission " + permission);
         }
     }
+
+    //=================================================================
+    // For logging
+
+    private static final SoundTriggerLogger sEventLogger = new SoundTriggerLogger(200,
+            "SoundTrigger activity");
+
 }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 2d8a280..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,
@@ -3371,8 +3371,7 @@
      * May throw an {@link IllegalArgumentException} if {@code overrideValues} contains invalid
      * values for the specified config keys.
      *
-     * NOTE: This API is meant for testing purposes only and may only be accessed from the shell UID
-     * during instrumentation testing.
+     * NOTE: This API is meant for testing purposes only.
      *
      * @param subscriptionId The subscription ID for which the override should be done.
      * @param overrideValues Key-value pairs of the values that are to be overridden. If set to
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 ccf49c9..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
@@ -3321,7 +3323,7 @@
     }
 
     /**
-     * Gets information about currently inserted UICCs and enabled eUICCs.
+     * Gets information about currently inserted UICCs and eUICCs.
      * <p>
      * Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
      * <p>
@@ -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.");
             }
diff --git a/telephony/java/android/telephony/UiccCardInfo.java b/telephony/java/android/telephony/UiccCardInfo.java
index 2c98c4d..b438920 100644
--- a/telephony/java/android/telephony/UiccCardInfo.java
+++ b/telephony/java/android/telephony/UiccCardInfo.java
@@ -101,7 +101,7 @@
 
     /**
      * Get the embedded ID (EID) of the eUICC. If the UiccCardInfo is not an eUICC
-     * (see {@link #isEuicc()}), returns null.
+     * (see {@link #isEuicc()}), or the EID is not available, returns null.
      * <p>
      * Note that this field may be omitted if the caller does not have the correct permissions
      * (see {@link TelephonyManager#getUiccCardsInfo()}).
@@ -115,7 +115,7 @@
     }
 
     /**
-     * Get the ICCID of the UICC.
+     * Get the ICCID of the UICC. If the ICCID is not availble, returns null.
      * <p>
      * Note that this field may be omitted if the caller does not have the correct permissions
      * (see {@link TelephonyManager#getUiccCardsInfo()}).
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 0169c26..f226bb1 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1594,7 +1594,7 @@
     int getCardIdForDefaultEuicc(int subId, String callingPackage);
 
     /**
-     * Gets information about currently inserted UICCs and enabled eUICCs.
+     * Gets information about currently inserted UICCs and eUICCs.
      * <p>
      * Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
      * <p>
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index dfc3b6e..e556b0a 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -88,6 +88,15 @@
     installable: false,
 }
 
+apex {
+    name: "com.android.tests.rollback.testapex.RollbackTestApexV3",
+    manifest: "TestApex/RollbackTestApexV3.json",
+    file_contexts: "apex.test",
+    prebuilts: ["RollbackTestApex.prebuilt.txt"],
+    key: "RollbackTestApex.key",
+    installable: false,
+}
+
 apex_key {
     name: "RollbackTestApex.key",
     public_key: "TestApex/com.android.tests.rollback.testapex.avbpubkey",
@@ -116,6 +125,7 @@
         ":RollbackTestAppASplitV2",
         ":com.android.tests.rollback.testapex.RollbackTestApexV1",
         ":com.android.tests.rollback.testapex.RollbackTestApexV2",
+        ":com.android.tests.rollback.testapex.RollbackTestApexV3",
     ],
     test_config: "RollbackTest.xml",
     sdk_version: "test_current",
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index 7e711c2..3b0e2a5 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -26,6 +26,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import org.junit.After;
@@ -54,6 +55,8 @@
             "com.android.tests.rollback.testapex.RollbackTestApexV1.apex";
     private static final String TEST_APEX_V2 =
             "com.android.tests.rollback.testapex.RollbackTestApexV2.apex";
+    private static final String TEST_APEX_V3 =
+            "com.android.tests.rollback.testapex.RollbackTestApexV3.apex";
 
     /**
      * Adopts common shell permissions needed for rollback tests.
@@ -145,26 +148,13 @@
 
     /**
      * Test rollbacks of staged installs an apk and an apex.
-     * Prepare apex (and apk) phase.
-     */
-    @Test
-    public void testApkAndApexPrepare() throws Exception {
-        RollbackTestUtils.uninstall(TEST_APP_A);
-        assertEquals(-1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
-
-        // Note: can't uninstall the apex. See note in #testApexOnlyPrepareApex().
-        RollbackTestUtils.installStaged(false, TEST_APP_A_V1, TEST_APEX_V1);
-
-        // At this point, the host test driver will reboot the device and run
-        // testApkAndApexEnableRollback().
-    }
-
-    /**
-     * Test rollbacks of staged installs an apk and an apex.
      * Enable rollback phase.
      */
     @Test
     public void testApkAndApexEnableRollback() throws Exception {
+        RollbackTestUtils.uninstall(TEST_APP_A);
+        RollbackTestUtils.install(TEST_APP_A_V1, false);
+
         assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APEX_PKG));
         assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
 
@@ -225,22 +215,6 @@
 
     /**
      * Test rollbacks of staged installs involving only apex.
-     * Prepare apex phase.
-     */
-    @Test
-    public void testApexOnlyPrepareApex() throws Exception {
-        // Note: We can't uninstall the apex if it is already on device,
-        // because that isn't supported yet (b/123667725). As long as nothing
-        // is failing, this should be fine because we don't expect the tests
-        // to leave the device with v2 of the apex installed.
-        RollbackTestUtils.installStaged(false, TEST_APEX_V1);
-
-        // At this point, the host test driver will reboot the device and run
-        // testApexOnlyEnableRollback().
-    }
-
-    /**
-     * Test rollbacks of staged installs involving only apex.
      * Enable rollback phase.
      */
     @Test
@@ -291,4 +265,51 @@
     public void testApexOnlyConfirmRollback() throws Exception {
         assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APEX_PKG));
     }
+
+    /**
+     * Tests that apex update expires existing rollbacks for that apex.
+     * Enable rollback phase.
+     */
+    @Test
+    public void testApexRollbackExpirationEnableRollback() throws Exception {
+        assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APEX_PKG));
+        RollbackTestUtils.installStaged(true, TEST_APEX_V2);
+
+        // At this point, the host test driver will reboot the device and run
+        // testApexRollbackExpirationUpdateApex().
+    }
+
+    /**
+     * Tests that apex update expires existing rollbacks for that apex.
+     * Update apex phase.
+     */
+    @Test
+    public void testApexRollbackExpirationUpdateApex() throws Exception {
+        assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APEX_PKG));
+        RollbackTestUtils.installStaged(false, TEST_APEX_V3);
+
+        // At this point, the host test driver will reboot the device and run
+        // testApexRollbackExpirationConfirmExpiration().
+    }
+
+    /**
+     * Tests that apex update expires existing rollbacks for that apex.
+     * Confirm expiration phase.
+     */
+    @Test
+    public void testApexRollbackExpirationConfirmExpiration() throws Exception {
+        assertEquals(3, RollbackTestUtils.getInstalledVersion(TEST_APEX_PKG));
+
+        RollbackManager rm = RollbackTestUtils.getRollbackManager();
+        assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APEX_PKG));
+    }
+
+    /**
+     * Helper function called by the host test to install v1 of the test apex,
+     * assuming the test apex is not installed.
+     */
+    @Test
+    public void installTestApexV1() throws Exception {
+        RollbackTestUtils.installStaged(false, TEST_APEX_V1);
+    }
 }
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index ac7f634..1f87ed8 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertTrue;
 
+import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
@@ -30,6 +31,8 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class StagedRollbackTest extends BaseHostJUnit4Test {
 
+    private static final String TEST_APEX_PKG = "com.android.tests.rollback.testapex";
+
     /**
      * Runs the given phase of a test by calling into the device.
      * Throws an exception if the test phase fails.
@@ -59,8 +62,7 @@
      */
     @Test
     public void testApexOnly() throws Exception {
-        runPhase("testApexOnlyPrepareApex");
-        getDevice().reboot();
+        installTestApexV1();
         runPhase("testApexOnlyEnableRollback");
         getDevice().reboot();
         runPhase("testApexOnlyCommitRollback");
@@ -73,12 +75,45 @@
      */
     @Test
     public void testApkAndApex() throws Exception {
-        runPhase("testApkAndApexPrepare");
-        getDevice().reboot();
+        installTestApexV1();
         runPhase("testApkAndApexEnableRollback");
         getDevice().reboot();
         runPhase("testApkAndApexCommitRollback");
         getDevice().reboot();
         runPhase("testApkAndApexConfirmRollback");
     }
+
+    /**
+     * Tests that apex update expires existing rollbacks for that apex.
+     */
+    @Test
+    public void testApexRollbackExpiration() throws Exception {
+        installTestApexV1();
+        runPhase("testApexRollbackExpirationEnableRollback");
+        getDevice().reboot();
+        runPhase("testApexRollbackExpirationUpdateApex");
+        getDevice().reboot();
+        runPhase("testApexRollbackExpirationConfirmExpiration");
+    }
+
+    /**
+     * Do whatever is necessary to get version 1 of the test apex installed on
+     * the device. Try to do so without extra reboots where possible to keep
+     * the test execution time down.
+     */
+    private void installTestApexV1() throws Exception {
+        for (ITestDevice.ApexInfo apexInfo : getDevice().getActiveApexes()) {
+            if (TEST_APEX_PKG.equals(apexInfo.name)) {
+                if (apexInfo.versionCode == 1) {
+                    return;
+                }
+                getDevice().uninstallPackage(TEST_APEX_PKG);
+                getDevice().reboot();
+                break;
+            }
+        }
+
+        runPhase("installTestApexV1");
+        getDevice().reboot();
+    }
 }
diff --git a/tests/RollbackTest/TestApex/RollbackTestApexV3.json b/tests/RollbackTest/TestApex/RollbackTestApexV3.json
new file mode 100644
index 0000000..87a2c9d
--- /dev/null
+++ b/tests/RollbackTest/TestApex/RollbackTestApexV3.json
@@ -0,0 +1,4 @@
+{
+    "name": "com.android.tests.rollback.testapex",
+    "version": 3
+}
diff --git a/tests/net/common/Android.bp b/tests/net/common/Android.bp
index 9ee5858..3b2e34a 100644
--- a/tests/net/common/Android.bp
+++ b/tests/net/common/Android.bp
@@ -18,7 +18,7 @@
 // They must be fast and stable, and exercise public or test APIs.
 java_library {
     name: "FrameworksNetCommonTests",
-    srcs: ["java/**/*.java"],
+    srcs: ["java/**/*.java", "java/**/*.kt"],
     static_libs: [
         "androidx.test.rules",
         "frameworks-net-testutils",
diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java
index 4177291..709f5f6 100644
--- a/tests/net/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/common/java/android/net/LinkPropertiesTest.java
@@ -868,12 +868,12 @@
 
         source.setNat64Prefix(new IpPrefix("2001:db8:1:2:64:64::/96"));
 
-        TestUtils.assertParcelingIsLossless(source, LinkProperties.CREATOR);
+        TestUtils.assertParcelingIsLossless(source);
     }
 
     @Test
     public void testParcelUninitialized() throws Exception {
         LinkProperties empty = new LinkProperties();
-        TestUtils.assertParcelingIsLossless(empty, LinkProperties.CREATOR);
+        TestUtils.assertParcelingIsLossless(empty);
     }
 }
diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
index ad76388..6bc7c1b 100644
--- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -33,6 +33,8 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
 import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkCapabilities.UNRESTRICTED_CAPABILITIES;
 
@@ -585,4 +587,20 @@
         nc2.set(nc1);  // Overwrites, as opposed to combineCapabilities
         assertEquals(nc1, nc2);
     }
+
+    @Test
+    public void testGetTransportTypes() {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        nc.addTransportType(TRANSPORT_CELLULAR);
+        nc.addTransportType(TRANSPORT_WIFI);
+        nc.addTransportType(TRANSPORT_VPN);
+        nc.addTransportType(TRANSPORT_TEST);
+
+        final int[] transportTypes = nc.getTransportTypes();
+        assertEquals(4, transportTypes.length);
+        assertEquals(TRANSPORT_CELLULAR, transportTypes[0]);
+        assertEquals(TRANSPORT_WIFI, transportTypes[1]);
+        assertEquals(TRANSPORT_VPN, transportTypes[2]);
+        assertEquals(TRANSPORT_TEST, transportTypes[3]);
+    }
 }
diff --git a/tests/net/common/java/android/net/NetworkTest.java b/tests/net/common/java/android/net/NetworkTest.java
index 0bee7cd..bef66b2 100644
--- a/tests/net/common/java/android/net/NetworkTest.java
+++ b/tests/net/common/java/android/net/NetworkTest.java
@@ -155,4 +155,12 @@
     private static <T> void assertNotEqual(T t1, T t2) {
         assertFalse(Objects.equals(t1, t2));
     }
+
+    @Test
+    public void testGetPrivateDnsBypassingCopy() {
+        final Network copy = mNetwork.getPrivateDnsBypassingCopy();
+        assertEquals(mNetwork.netId, copy.netId);
+        assertNotEqual(copy.netId, copy.getNetIdForResolv());
+        assertNotEqual(mNetwork.getNetIdForResolv(), copy.getNetIdForResolv());
+    }
 }
diff --git a/tests/net/common/java/android/net/StaticIpConfigurationTest.java b/tests/net/common/java/android/net/StaticIpConfigurationTest.java
index 8449ca7..5096be2 100644
--- a/tests/net/common/java/android/net/StaticIpConfigurationTest.java
+++ b/tests/net/common/java/android/net/StaticIpConfigurationTest.java
@@ -31,7 +31,9 @@
 import org.junit.runner.RunWith;
 
 import java.net.InetAddress;
+import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Objects;
 
 @RunWith(AndroidJUnit4.class)
@@ -46,6 +48,7 @@
     private static final InetAddress DNS2 = IpAddress("8.8.4.4");
     private static final InetAddress DNS3 = IpAddress("4.2.2.2");
     private static final String IFACE = "eth0";
+    private static final String FAKE_DOMAINS = "google.com";
 
     private static InetAddress IpAddress(String addr) {
         return InetAddress.parseNumericAddress(addr);
@@ -69,7 +72,7 @@
         s.dnsServers.add(DNS1);
         s.dnsServers.add(DNS2);
         s.dnsServers.add(DNS3);
-        s.domains = "google.com";
+        s.domains = FAKE_DOMAINS;
         return s;
     }
 
@@ -178,8 +181,8 @@
         expected.addDnsServer(DNS3);
         assertEquals(expected, s.toLinkProperties(IFACE));
 
-        s.domains = "google.com";
-        expected.setDomains("google.com");
+        s.domains = FAKE_DOMAINS;
+        expected.setDomains(FAKE_DOMAINS);
         assertEquals(expected, s.toLinkProperties(IFACE));
 
         s.gateway = null;
@@ -218,4 +221,53 @@
         StaticIpConfiguration s2 = passThroughParcel(s);
         assertEquals(s, s2);
     }
+
+    @Test
+    public void testBuilder() {
+        final ArrayList<InetAddress> dnsServers = new ArrayList<>();
+        dnsServers.add(DNS1);
+
+        final StaticIpConfiguration s = new StaticIpConfiguration.Builder()
+                .setIpAddress(ADDR)
+                .setGateway(GATEWAY)
+                .setDomains(FAKE_DOMAINS)
+                .setDnsServers(dnsServers)
+                .build();
+
+        assertEquals(s.ipAddress, s.getIpAddress());
+        assertEquals(ADDR, s.getIpAddress());
+        assertEquals(s.gateway, s.getGateway());
+        assertEquals(GATEWAY, s.getGateway());
+        assertEquals(s.domains, s.getDomains());
+        assertEquals(FAKE_DOMAINS, s.getDomains());
+        assertTrue(s.dnsServers.equals(s.getDnsServers()));
+        assertEquals(1, s.getDnsServers().size());
+        assertEquals(DNS1, s.getDnsServers().get(0));
+    }
+
+    @Test
+    public void testAddDnsServers() {
+        final StaticIpConfiguration s = new StaticIpConfiguration((StaticIpConfiguration) null);
+        checkEmpty(s);
+
+        s.addDnsServer(DNS1);
+        assertEquals(1, s.getDnsServers().size());
+        assertEquals(DNS1, s.getDnsServers().get(0));
+
+        s.addDnsServer(DNS2);
+        s.addDnsServer(DNS3);
+        assertEquals(3, s.getDnsServers().size());
+        assertEquals(DNS2, s.getDnsServers().get(1));
+        assertEquals(DNS3, s.getDnsServers().get(2));
+    }
+
+    @Test
+    public void testGetRoutes() {
+        final StaticIpConfiguration s = makeTestObject();
+        final List<RouteInfo> routeInfoList = s.getRoutes(IFACE);
+
+        assertEquals(2, routeInfoList.size());
+        assertEquals(new RouteInfo(ADDR, (InetAddress) null, IFACE), routeInfoList.get(0));
+        assertEquals(new RouteInfo((IpPrefix) null, GATEWAY, IFACE), routeInfoList.get(1));
+    }
 }
diff --git a/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java b/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java
index 7238895..3ed8a86 100644
--- a/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java
@@ -36,7 +36,7 @@
         final ApfCapabilities caps = new ApfCapabilities(123, 456, 789);
         ParcelableTestUtil.assertFieldCountEquals(3, ApfCapabilities.class);
 
-        TestUtils.assertParcelingIsLossless(caps, ApfCapabilities.CREATOR);
+        TestUtils.assertParcelingIsLossless(caps);
     }
 
     @Test
diff --git a/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt b/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt
new file mode 100644
index 0000000..e191953
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt
@@ -0,0 +1,67 @@
+package android.net.metrics
+
+import android.net.metrics.DhcpErrorEvent.errorCodeWithOption
+import android.net.metrics.DhcpErrorEvent.DHCP_INVALID_OPTION_LENGTH
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.internal.util.TestUtils.parcelingRoundTrip
+import java.lang.reflect.Modifier
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val TEST_ERROR_CODE = 12345
+/**
+ * DHCP Optional Type: DHCP Subnet Mask (Copy from DhcpPacket.java)
+ */
+private const val DHCP_SUBNET_MASK = 1
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class DhcpErrorEventTest {
+
+    @Test
+    fun testConstructor() {
+        val event = DhcpErrorEvent(TEST_ERROR_CODE)
+        assertEquals(TEST_ERROR_CODE, event.errorCode)
+    }
+
+    @Test
+    fun testParcelUnparcel() {
+        val event = DhcpErrorEvent(TEST_ERROR_CODE)
+        val parceled = parcelingRoundTrip(event)
+        assertEquals(TEST_ERROR_CODE, parceled.errorCode)
+    }
+
+    @Test
+    fun testErrorCodeWithOption() {
+        val errorCode = errorCodeWithOption(DHCP_INVALID_OPTION_LENGTH, DHCP_SUBNET_MASK);
+        assertTrue((DHCP_INVALID_OPTION_LENGTH and errorCode) == DHCP_INVALID_OPTION_LENGTH);
+        assertTrue((DHCP_SUBNET_MASK and errorCode) == DHCP_SUBNET_MASK);
+    }
+
+    @Test
+    fun testToString() {
+        val names = listOf("L2_ERROR", "L3_ERROR", "L4_ERROR", "DHCP_ERROR", "MISC_ERROR")
+        val errorFields = DhcpErrorEvent::class.java.declaredFields.filter {
+            it.type == Int::class.javaPrimitiveType
+                    && Modifier.isPublic(it.modifiers) && Modifier.isStatic(it.modifiers)
+                    && it.name !in names
+        }
+
+        errorFields.forEach {
+            val intValue = it.getInt(null)
+            val stringValue = DhcpErrorEvent(intValue).toString()
+            assertTrue("Invalid string for error 0x%08X (field %s): %s".format(intValue, it.name,
+                    stringValue),
+                    stringValue.contains(it.name))
+        }
+    }
+
+    @Test
+    fun testToString_InvalidErrorCode() {
+        assertNotNull(DhcpErrorEvent(TEST_ERROR_CODE).toString())
+    }
+}
\ No newline at end of file
diff --git a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
index e0b7227..583d3fd 100644
--- a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
+++ b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
@@ -79,7 +79,7 @@
         assertEquals(testInfo.tos, resultData.ipTos);
         assertEquals(testInfo.ttl, resultData.ipTtl);
 
-        TestUtils.assertParcelingIsLossless(resultData, TcpKeepalivePacketData.CREATOR);
+        TestUtils.assertParcelingIsLossless(resultData);
 
         final byte[] packet = resultData.getPacket();
         // IP version and IHL
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index c15775f..363ac9c 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -567,6 +567,16 @@
                 protected void preventAutomaticReconnect() {
                     mPreventReconnectReceived.open();
                 }
+
+                @Override
+                protected void addKeepalivePacketFilter(Message msg) {
+                    Log.i(TAG, "Add keepalive packet filter.");
+                }
+
+                @Override
+                protected void removeKeepalivePacketFilter(Message msg) {
+                    Log.i(TAG, "Remove keepalive packet filter.");
+                }
             };
 
             assertEquals(mNetworkAgent.netId, nmNetworkCaptor.getValue().netId);
diff --git a/tests/net/util/java/com/android/internal/util/TestUtils.java b/tests/net/util/java/com/android/internal/util/TestUtils.java
index 75329a8..a99cd47 100644
--- a/tests/net/util/java/com/android/internal/util/TestUtils.java
+++ b/tests/net/util/java/com/android/internal/util/TestUtils.java
@@ -69,9 +69,17 @@
         }
     }
 
-    // TODO : fetch the creator through reflection or something instead of passing it
-    public static <T extends Parcelable, C extends Parcelable.Creator<T>>
-            void assertParcelingIsLossless(T source, C creator) {
+    /**
+     * Return a new instance of {@code T} after being parceled then unparceled.
+     */
+    public static <T extends Parcelable> T parcelingRoundTrip(T source) {
+        final Parcelable.Creator<T> creator;
+        try {
+            creator = (Parcelable.Creator<T>) source.getClass().getField("CREATOR").get(null);
+        } catch (IllegalAccessException | NoSuchFieldException e) {
+            fail("Missing CREATOR field: " + e.getMessage());
+            return null;
+        }
         Parcel p = Parcel.obtain();
         source.writeToParcel(p, /* flags */ 0);
         p.setDataPosition(0);
@@ -79,7 +87,14 @@
         p = Parcel.obtain();
         p.unmarshall(marshalled, 0, marshalled.length);
         p.setDataPosition(0);
-        T dest = creator.createFromParcel(p);
-        assertEquals(source, dest);
+        return creator.createFromParcel(p);
+    }
+
+    /**
+     * Assert that after being parceled then unparceled, {@code source} is equal to the original
+     * object.
+     */
+    public static <T extends Parcelable> void assertParcelingIsLossless(T source) {
+        assertEquals(source, parcelingRoundTrip(source));
     }
 }
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index 97ca6dc..f41426d 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -2132,6 +2132,10 @@
 
     # Remove all existing things so we're left with new
     for prev_clazz in prev.values():
+        if prev_clazz.fullname not in cur:
+            # The class was removed this release; we can safely ignore it.
+            continue
+
         cur_clazz = cur[prev_clazz.fullname]
         if not is_interesting(cur_clazz): continue