Merge "Fix registering PiP transition calback in PipManager multiple times" into rvc-dev
diff --git a/api/test-current.txt b/api/test-current.txt
index 5de6c2a..641767c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -214,6 +214,7 @@
     field public static final String OPSTR_GPS = "android:gps";
     field public static final String OPSTR_INSTANT_APP_START_FOREGROUND = "android:instant_app_start_foreground";
     field public static final String OPSTR_LEGACY_STORAGE = "android:legacy_storage";
+    field public static final String OPSTR_MANAGE_EXTERNAL_STORAGE = "android:manage_external_storage";
     field public static final String OPSTR_MANAGE_IPSEC_TUNNELS = "android:manage_ipsec_tunnels";
     field public static final String OPSTR_MUTE_MICROPHONE = "android:mute_microphone";
     field public static final String OPSTR_NEIGHBORING_CELLS = "android:neighboring_cells";
@@ -4860,7 +4861,9 @@
   }
 
   public final class SurfaceControl implements android.os.Parcelable {
+    ctor public SurfaceControl(@NonNull android.view.SurfaceControl);
     method public static long acquireFrameRateFlexibilityToken();
+    method public boolean isSameSurface(@NonNull android.view.SurfaceControl);
     method public static void releaseFrameRateFlexibilityToken(long);
   }
 
@@ -5213,10 +5216,10 @@
     method public void onDisplayAreaAppeared(@NonNull android.window.WindowContainerToken);
     method public void onDisplayAreaVanished(@NonNull android.window.WindowContainerToken);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void registerOrganizer(int);
+    field public static final int FEATURE_DEFAULT_TASK_CONTAINER = 1; // 0x1
     field public static final int FEATURE_ROOT = 0; // 0x0
     field public static final int FEATURE_SYSTEM_FIRST = 0; // 0x0
     field public static final int FEATURE_SYSTEM_LAST = 10000; // 0x2710
-    field public static final int FEATURE_TASK_CONTAINER = 1; // 0x1
     field public static final int FEATURE_UNDEFINED = -1; // 0xffffffff
     field public static final int FEATURE_VENDOR_FIRST = 10001; // 0x2711
     field public static final int FEATURE_WINDOW_TOKENS = 2; // 0x2
diff --git a/cmds/statsd/benchmark/filter_value_benchmark.cpp b/cmds/statsd/benchmark/filter_value_benchmark.cpp
index 28bf21a..743ccc4 100644
--- a/cmds/statsd/benchmark/filter_value_benchmark.cpp
+++ b/cmds/statsd/benchmark/filter_value_benchmark.cpp
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 #include <vector>
-#include "benchmark/benchmark.h"
+
 #include "FieldValue.h"
 #include "HashableDimensionKey.h"
+#include "benchmark/benchmark.h"
 #include "logd/LogEvent.h"
-#include "stats_log_util.h"
+#include "metric_util.h"
 #include "stats_event.h"
+#include "stats_log_util.h"
 
 namespace android {
 namespace os {
@@ -34,24 +36,13 @@
 
     std::vector<int> attributionUids = {100, 100};
     std::vector<string> attributionTags = {"LOCATION", "LOCATION"};
+    writeAttribution(statsEvent, attributionUids, attributionTags);
 
-    vector<const char*> cTags(attributionTags.size());
-    for (int i = 0; i < cTags.size(); i++) {
-        cTags[i] = attributionTags[i].c_str();
-    }
-
-    AStatsEvent_writeAttributionChain(statsEvent,
-                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
-                                      cTags.data(), attributionUids.size());
     AStatsEvent_writeFloat(statsEvent, 3.2f);
     AStatsEvent_writeString(statsEvent, "LOCATION");
     AStatsEvent_writeInt64(statsEvent, 990);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    event->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, event);
 
     field_matcher->set_field(1);
     auto child = field_matcher->add_child();
diff --git a/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp b/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp
index c7d01cc..7a45565 100644
--- a/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp
+++ b/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 #include <vector>
-#include "benchmark/benchmark.h"
+
 #include "FieldValue.h"
 #include "HashableDimensionKey.h"
+#include "benchmark/benchmark.h"
 #include "logd/LogEvent.h"
-#include "stats_log_util.h"
+#include "metric_util.h"
 #include "stats_event.h"
+#include "stats_log_util.h"
 
 namespace android {
 namespace os {
@@ -34,24 +36,13 @@
 
     std::vector<int> attributionUids = {100, 100};
     std::vector<string> attributionTags = {"LOCATION", "LOCATION"};
+    writeAttribution(statsEvent, attributionUids, attributionTags);
 
-    vector<const char*> cTags(attributionTags.size());
-    for (int i = 0; i < cTags.size(); i++) {
-        cTags[i] = attributionTags[i].c_str();
-    }
-
-    AStatsEvent_writeAttributionChain(statsEvent,
-                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
-                                      cTags.data(), attributionUids.size());
     AStatsEvent_writeFloat(statsEvent, 3.2f);
     AStatsEvent_writeString(statsEvent, "LOCATION");
     AStatsEvent_writeInt64(statsEvent, 990);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    event->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, event);
 
     link->conditionId = 1;
 
diff --git a/cmds/statsd/benchmark/metric_util.cpp b/cmds/statsd/benchmark/metric_util.cpp
index 482d66f..89fd3d9 100644
--- a/cmds/statsd/benchmark/metric_util.cpp
+++ b/cmds/statsd/benchmark/metric_util.cpp
@@ -247,21 +247,37 @@
     return dimensions;
 }
 
+void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids,
+                      const vector<string>& attributionTags) {
+    vector<const char*> cTags(attributionTags.size());
+    for (int i = 0; i < cTags.size(); i++) {
+        cTags[i] = attributionTags[i].c_str();
+    }
+
+    AStatsEvent_writeAttributionChain(statsEvent,
+                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
+                                      cTags.data(), attributionUids.size());
+}
+
+void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent) {
+    AStatsEvent_build(statsEvent);
+
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+    logEvent->parseBuffer(buf, size);
+
+    AStatsEvent_release(statsEvent);
+}
+
 std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
         uint64_t timestampNs, const android::view::DisplayStateEnum state) {
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, util::SCREEN_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -272,24 +288,12 @@
     AStatsEvent_setAtomId(statsEvent, util::SCHEDULED_JOB_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
 
-    vector<const char*> cTags(attributionTags.size());
-    for (int i = 0; i < cTags.size(); i++) {
-        cTags[i] = attributionTags[i].c_str();
-    }
-
-    AStatsEvent_writeAttributionChain(statsEvent,
-                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
-                                      cTags.data(), attributionUids.size());
+    writeAttribution(statsEvent, attributionUids, attributionTags);
     AStatsEvent_writeString(statsEvent, jobName.c_str());
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -319,24 +323,12 @@
     AStatsEvent_setAtomId(statsEvent, util::SYNC_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
 
-    vector<const char*> cTags(attributionTags.size());
-    for (int i = 0; i < cTags.size(); i++) {
-        cTags[i] = attributionTags[i].c_str();
-    }
-
-    AStatsEvent_writeAttributionChain(statsEvent,
-                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
-                                      cTags.data(), attributionUids.size());
+    writeAttribution(statsEvent, attributionUids, attributionTags);
     AStatsEvent_writeString(statsEvent, name.c_str());
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
diff --git a/cmds/statsd/benchmark/metric_util.h b/cmds/statsd/benchmark/metric_util.h
index c5fcf7c..3efaa85 100644
--- a/cmds/statsd/benchmark/metric_util.h
+++ b/cmds/statsd/benchmark/metric_util.h
@@ -18,6 +18,7 @@
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "src/StatsLogProcessor.h"
 #include "src/logd/LogEvent.h"
+#include "stats_event.h"
 #include "statslog.h"
 
 namespace android {
@@ -92,6 +93,11 @@
 FieldMatcher CreateAttributionUidDimensions(const int atomId,
                                             const std::vector<Position>& positions);
 
+void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids,
+                      const vector<string>& attributionTags);
+
+void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent);
+
 // Create log event for screen state changed.
 std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
         uint64_t timestampNs, const android::view::DisplayStateEnum state);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index bd15264..d1995bc 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -418,7 +418,7 @@
         AppStandbyBucketChanged app_standby_bucket_changed = 258 [(module) = "framework"];
         SharesheetStarted sharesheet_started = 259 [(module) = "framework"];
         RankingSelected ranking_selected = 260 [(module) = "framework"];
-        TvSettingsUIInteracted tvsettings_ui_interacted = 261;
+        TvSettingsUIInteracted tvsettings_ui_interacted = 261 [(module) = "tv_settings"];
         SdkExtensionStatus sdk_extension_status = 354;
     }
 
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 6d9c644..bbae3fe 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -43,20 +43,23 @@
 using std::unique_ptr;
 
 struct ConfigReceiverDeathCookie {
-    ConfigReceiverDeathCookie(sp<ConfigManager> configManager, const ConfigKey& configKey,
-                              const shared_ptr<IPendingIntentRef>& pir):
-        mConfigManager(configManager),
-        mConfigKey(configKey),
-        mPir(pir) {}
+    ConfigReceiverDeathCookie(const wp<ConfigManager>& configManager, const ConfigKey& configKey,
+                              const shared_ptr<IPendingIntentRef>& pir) :
+            mConfigManager(configManager), mConfigKey(configKey), mPir(pir) {
+    }
 
-    sp<ConfigManager> mConfigManager;
+    wp<ConfigManager> mConfigManager;
     ConfigKey mConfigKey;
     shared_ptr<IPendingIntentRef> mPir;
 };
 
 void ConfigManager::configReceiverDied(void* cookie) {
     auto cookie_ = static_cast<ConfigReceiverDeathCookie*>(cookie);
-    sp<ConfigManager>& thiz = cookie_->mConfigManager;
+    sp<ConfigManager> thiz = cookie_->mConfigManager.promote();
+    if (!thiz) {
+        return;
+    }
+
     ConfigKey& configKey = cookie_->mConfigKey;
     shared_ptr<IPendingIntentRef>& pir = cookie_->mPir;
 
@@ -74,20 +77,23 @@
 }
 
 struct ActiveConfigChangedReceiverDeathCookie {
-    ActiveConfigChangedReceiverDeathCookie(sp<ConfigManager> configManager, const int uid,
-                                           const shared_ptr<IPendingIntentRef>& pir):
-        mConfigManager(configManager),
-        mUid(uid),
-        mPir(pir) {}
+    ActiveConfigChangedReceiverDeathCookie(const wp<ConfigManager>& configManager, const int uid,
+                                           const shared_ptr<IPendingIntentRef>& pir) :
+            mConfigManager(configManager), mUid(uid), mPir(pir) {
+    }
 
-    sp<ConfigManager> mConfigManager;
+    wp<ConfigManager> mConfigManager;
     int mUid;
     shared_ptr<IPendingIntentRef> mPir;
 };
 
 void ConfigManager::activeConfigChangedReceiverDied(void* cookie) {
     auto cookie_ = static_cast<ActiveConfigChangedReceiverDeathCookie*>(cookie);
-    sp<ConfigManager>& thiz = cookie_->mConfigManager;
+    sp<ConfigManager> thiz = cookie_->mConfigManager.promote();
+    if (!thiz) {
+        return;
+    }
+
     int uid = cookie_->mUid;
     shared_ptr<IPendingIntentRef>& pir = cookie_->mPir;
 
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 79a7e8d..ebe9610 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -44,19 +44,23 @@
 // Stores the puller as a wp to avoid holding a reference in case it is unregistered and
 // pullAtomCallbackDied is never called.
 struct PullAtomCallbackDeathCookie {
-    PullAtomCallbackDeathCookie(sp<StatsPullerManager> pullerManager, const PullerKey& pullerKey,
-                                const wp<StatsPuller>& puller)
-        : mPullerManager(pullerManager), mPullerKey(pullerKey), mPuller(puller) {
+    PullAtomCallbackDeathCookie(const wp<StatsPullerManager>& pullerManager,
+                                const PullerKey& pullerKey, const wp<StatsPuller>& puller) :
+            mPullerManager(pullerManager), mPullerKey(pullerKey), mPuller(puller) {
     }
 
-    sp<StatsPullerManager> mPullerManager;
+    wp<StatsPullerManager> mPullerManager;
     PullerKey mPullerKey;
     wp<StatsPuller> mPuller;
 };
 
 void StatsPullerManager::pullAtomCallbackDied(void* cookie) {
     PullAtomCallbackDeathCookie* cookie_ = static_cast<PullAtomCallbackDeathCookie*>(cookie);
-    sp<StatsPullerManager>& thiz = cookie_->mPullerManager;
+    sp<StatsPullerManager> thiz = cookie_->mPullerManager.promote();
+    if (!thiz) {
+        return;
+    }
+
     const PullerKey& pullerKey = cookie_->mPullerKey;
     wp<StatsPuller> puller = cookie_->mPuller;
 
diff --git a/cmds/statsd/tests/FieldValue_test.cpp b/cmds/statsd/tests/FieldValue_test.cpp
index 0bf24f1..f5ba8fd 100644
--- a/cmds/statsd/tests/FieldValue_test.cpp
+++ b/cmds/statsd/tests/FieldValue_test.cpp
@@ -41,22 +41,10 @@
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
 
-    vector<const char*> cTags(attributionTags.size());
-    for (int i = 0; i < cTags.size(); i++) {
-        cTags[i] = attributionTags[i].c_str();
-    }
-
-    AStatsEvent_writeAttributionChain(statsEvent,
-                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
-                                      cTags.data(), attributionUids.size());
+    writeAttribution(statsEvent, attributionUids, attributionTags);
     AStatsEvent_writeString(statsEvent, name.c_str());
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
 void makeLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
@@ -66,22 +54,10 @@
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
 
-    vector<const char*> cTags(attributionTags.size());
-    for (int i = 0; i < cTags.size(); i++) {
-        cTags[i] = attributionTags[i].c_str();
-    }
-
-    AStatsEvent_writeAttributionChain(statsEvent,
-                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
-                                      cTags.data(), attributionUids.size());
+    writeAttribution(statsEvent, attributionUids, attributionTags);
     AStatsEvent_writeInt32(statsEvent, value);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 }  // anonymous namespace
 
diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp
index 6f4c8e4..9cdf582 100644
--- a/cmds/statsd/tests/LogEntryMatcher_test.cpp
+++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp
@@ -48,15 +48,9 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
-
     AStatsEvent_writeInt32(statsEvent, value);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
 void makeFloatLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
@@ -64,15 +58,9 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
-
     AStatsEvent_writeFloat(statsEvent, floatValue);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
 void makeStringLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
@@ -80,15 +68,9 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
-
     AStatsEvent_writeString(statsEvent, name.c_str());
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
 void makeIntStringLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
@@ -99,13 +81,8 @@
 
     AStatsEvent_writeInt32(statsEvent, value);
     AStatsEvent_writeString(statsEvent, name.c_str());
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
 void makeAttributionLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
@@ -115,22 +92,10 @@
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
 
-    vector<const char*> cTags(attributionTags.size());
-    for (int i = 0; i < cTags.size(); i++) {
-        cTags[i] = attributionTags[i].c_str();
-    }
-
-    AStatsEvent_writeAttributionChain(statsEvent,
-                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
-                                      cTags.data(), attributionUids.size());
+    writeAttribution(statsEvent, attributionUids, attributionTags);
     AStatsEvent_writeString(statsEvent, name.c_str());
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
 void makeBoolLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
@@ -141,13 +106,8 @@
 
     AStatsEvent_writeBool(statsEvent, bool1);
     AStatsEvent_writeBool(statsEvent, bool2);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
 }  // anonymous namespace
diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
index 7febb35..ba5b032 100644
--- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
+++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
@@ -67,22 +67,12 @@
     AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
 
     vector<std::string> tags(uids.size()); // vector of empty strings
-    vector<const char*> cTags(uids.size());
-    for (int i = 0; i < cTags.size(); i++) {
-        cTags[i] = tags[i].c_str();
-    }
-    AStatsEvent_writeAttributionChain(statsEvent, reinterpret_cast<const uint32_t*>(uids.data()),
-                                      cTags.data(), uids.size());
+    writeAttribution(statsEvent, uids, tags);
 
     AStatsEvent_writeString(statsEvent, wl.c_str());
     AStatsEvent_writeInt32(statsEvent, acquire);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
 } // anonymous namespace
diff --git a/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp b/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
index 81e1c05..60403f2 100644
--- a/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
+++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
@@ -84,14 +84,9 @@
     AStatsEvent_writeString(statsEvent, calling_pkg_name.c_str());
     AStatsEvent_writeInt32(statsEvent, is_instant_app);
     AStatsEvent_writeInt32(statsEvent, activity_start_msec);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
diff --git a/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
index 6aff9ef..4b9bac1 100644
--- a/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
+++ b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
@@ -190,13 +190,13 @@
     int32_t uid = 123;
     values.push_back(value);
 
-    StatsPullerManager pullerManager;
-    pullerManager.RegisterPullAtomCallback(uid, pullTagId, pullCoolDownNs, pullTimeoutNs,
-                                           vector<int32_t>(), cb);
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    pullerManager->RegisterPullAtomCallback(uid, pullTagId, pullCoolDownNs, pullTimeoutNs,
+                                            vector<int32_t>(), cb);
     vector<shared_ptr<LogEvent>> dataHolder;
     int64_t startTimeNs = getElapsedRealtimeNs();
     // Returns false, since StatsPuller code will evaluate the timeout.
-    EXPECT_FALSE(pullerManager.Pull(pullTagId, {uid}, &dataHolder));
+    EXPECT_FALSE(pullerManager->Pull(pullTagId, {uid}, &dataHolder));
     int64_t endTimeNs = getElapsedRealtimeNs();
     int64_t actualPullDurationNs = endTimeNs - startTimeNs;
 
diff --git a/cmds/statsd/tests/external/StatsPuller_test.cpp b/cmds/statsd/tests/external/StatsPuller_test.cpp
index e8200d5..5043358 100644
--- a/cmds/statsd/tests/external/StatsPuller_test.cpp
+++ b/cmds/statsd/tests/external/StatsPuller_test.cpp
@@ -64,16 +64,10 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, pullTagId);
     AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
-
     AStatsEvent_writeInt64(statsEvent, value);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
diff --git a/cmds/statsd/tests/external/puller_util_test.cpp b/cmds/statsd/tests/external/puller_util_test.cpp
index 15425d8..3e1cc5e 100644
--- a/cmds/statsd/tests/external/puller_util_test.cpp
+++ b/cmds/statsd/tests/external/puller_util_test.cpp
@@ -23,6 +23,7 @@
 #include "../metrics/metrics_test_helper.h"
 #include "stats_event.h"
 #include "statslog_statsdtest.h"
+#include "tests/statsd_test_util.h"
 
 #ifdef __ANDROID__
 
@@ -71,14 +72,9 @@
     AStatsEvent_writeInt32(statsEvent, uid);
     AStatsEvent_writeInt32(statsEvent, data1);
     AStatsEvent_writeInt32(statsEvent, data2);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::shared_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -86,16 +82,10 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, nonUidAtomTagId);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
     AStatsEvent_writeInt32(statsEvent, data1);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::shared_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
diff --git a/cmds/statsd/tests/log_event/LogEventQueue_test.cpp b/cmds/statsd/tests/log_event/LogEventQueue_test.cpp
index 6dc041f..a15f95b 100644
--- a/cmds/statsd/tests/log_event/LogEventQueue_test.cpp
+++ b/cmds/statsd/tests/log_event/LogEventQueue_test.cpp
@@ -21,6 +21,7 @@
 #include <thread>
 
 #include "stats_event.h"
+#include "tests/statsd_test_util.h"
 
 namespace android {
 namespace os {
@@ -37,14 +38,9 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, 10);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index d55996c..65f8de6 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -46,26 +46,17 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
 void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId, string uid) {
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
     AStatsEvent_writeString(statsEvent, uid.c_str());
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
 }  // namespace
diff --git a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
index 6143dc0..30f8159 100644
--- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
@@ -26,6 +26,7 @@
 #include "src/condition/ConditionWizard.h"
 #include "src/stats_log_util.h"
 #include "stats_event.h"
+#include "tests/statsd_test_util.h"
 
 using namespace android::os::statsd;
 using namespace testing;
@@ -48,12 +49,8 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
 }  // namespace
diff --git a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
index e58bbb7..97647a7 100644
--- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
@@ -43,14 +43,9 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
     AStatsEvent_writeString(statsEvent, str.c_str());
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 }  // anonymous namespace
 
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index 2fe05a4..42d0d5d 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -64,14 +64,9 @@
     AStatsEvent_writeInt32(statsEvent, value1);
     AStatsEvent_writeString(statsEvent, str1.c_str());
     AStatsEvent_writeInt32(statsEvent, value2);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
     shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
-
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 }  // anonymous namespace
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index b623a09..009e49a 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -611,7 +611,7 @@
 
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
-    allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 110));
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 110));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
     assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8});
 
@@ -656,7 +656,7 @@
     valueProducer.prepareFirstBucket();
 
     LogEvent event1(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10);
+    CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
 
@@ -665,14 +665,14 @@
     EXPECT_EQ(bucketStartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs);
 
     LogEvent event2(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 59 * NS_PER_SEC, 1, 10);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 59 * NS_PER_SEC, 10);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
     EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
     EXPECT_EQ(bucketStartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs);
 
     // Next value should create a new bucket.
     LogEvent event3(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event3, tagId, bucketStartTimeNs + 65 * NS_PER_SEC, 1, 10);
+    CreateRepeatedValueLogEvent(&event3, tagId, bucketStartTimeNs + 65 * NS_PER_SEC, 10);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
     EXPECT_EQ(2UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, valueProducer.mCurrentBucketStartTimeNs);
@@ -812,10 +812,10 @@
     valueProducer.prepareFirstBucket();
 
     LogEvent event1(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10);
+    CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
 
     LogEvent event2(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 20);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20);
 
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     // has one slice
@@ -856,7 +856,7 @@
     valueProducer.mCondition = ConditionState::kFalse;
 
     LogEvent event1(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10);
+    CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     // has 1 slice
     EXPECT_EQ(0UL, valueProducer.mCurrentSlicedBucket.size());
@@ -864,7 +864,7 @@
     valueProducer.onConditionChangedLocked(true, bucketStartTimeNs + 15);
 
     LogEvent event2(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 20);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
 
     // has one slice
@@ -875,7 +875,7 @@
     EXPECT_EQ(20, curInterval.value.long_value);
 
     LogEvent event3(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event3, tagId, bucketStartTimeNs + 30, 1, 30);
+    CreateRepeatedValueLogEvent(&event3, tagId, bucketStartTimeNs + 30, 30);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
 
     // has one slice
@@ -886,7 +886,7 @@
     valueProducer.onConditionChangedLocked(false, bucketStartTimeNs + 35);
 
     LogEvent event4(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event4, tagId, bucketStartTimeNs + 40, 1, 40);
+    CreateRepeatedValueLogEvent(&event4, tagId, bucketStartTimeNs + 40, 40);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4);
 
     // has one slice
@@ -1195,10 +1195,10 @@
     valueProducer.prepareFirstBucket();
 
     LogEvent event1(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10);
+    CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
 
     LogEvent event2(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 20);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20);
 
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     // has one slice
@@ -1238,10 +1238,10 @@
     valueProducer.prepareFirstBucket();
 
     LogEvent event1(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10);
+    CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
 
     LogEvent event2(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 20);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
 
     // has one slice
@@ -1283,10 +1283,10 @@
     valueProducer.prepareFirstBucket();
 
     LogEvent event1(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10);
+    CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
 
     LogEvent event2(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 15);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 15);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     // has one slice
     EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
@@ -1331,10 +1331,10 @@
     valueProducer.prepareFirstBucket();
 
     LogEvent event1(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10);
+    CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
 
     LogEvent event2(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 15);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 15);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     // has one slice
     EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
@@ -1374,10 +1374,10 @@
     valueProducer.prepareFirstBucket();
 
     LogEvent event1(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10);
+    CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
 
     LogEvent event2(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 15, 1, 15);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 15, 15);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     // has one slice
     EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
@@ -1398,7 +1398,7 @@
 
     // no change in data.
     LogEvent event3(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event3, tagId, bucket2StartTimeNs + 10, 1, 15);
+    CreateRepeatedValueLogEvent(&event3, tagId, bucket2StartTimeNs + 10, 15);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
     EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
@@ -1408,7 +1408,7 @@
     EXPECT_EQ(true, curInterval.hasValue);
 
     LogEvent event4(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event4, tagId, bucket2StartTimeNs + 15, 1, 15);
+    CreateRepeatedValueLogEvent(&event4, tagId, bucket2StartTimeNs + 15, 15);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4);
     EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
@@ -2166,7 +2166,7 @@
     // Bucket start.
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
-    allData.push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 1, 1, 110));
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 110));
     valueProducer->onDataPulled(allData, /** succeed */ false, bucketStartTimeNs);
 
     valueProducer->onConditionChanged(false, bucketStartTimeNs + 2);
@@ -2174,7 +2174,7 @@
 
     // Bucket end.
     allData.clear();
-    allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 140));
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 140));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
 
     valueProducer->flushIfNeededLocked(bucket2StartTimeNs + 1);
diff --git a/cmds/statsd/tests/shell/ShellSubscriber_test.cpp b/cmds/statsd/tests/shell/ShellSubscriber_test.cpp
index ac3ad69..7b952d7 100644
--- a/cmds/statsd/tests/shell/ShellSubscriber_test.cpp
+++ b/cmds/statsd/tests/shell/ShellSubscriber_test.cpp
@@ -171,13 +171,9 @@
     AStatsEvent_overwriteTimestamp(statsEvent, 1111L);
     AStatsEvent_writeInt32(statsEvent, uid);
     AStatsEvent_writeInt64(statsEvent, timeMillis);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp
index a5b8e1c..78c80bc 100644
--- a/cmds/statsd/tests/state/StateTracker_test.cpp
+++ b/cmds/statsd/tests/state/StateTracker_test.cpp
@@ -62,7 +62,7 @@
 
 // START: build event functions.
 // Incorrect event - missing fields
-std::shared_ptr<LogEvent> buildIncorrectOverlayEvent(int uid, const std::string& packageName,
+std::unique_ptr<LogEvent> buildIncorrectOverlayEvent(int uid, const std::string& packageName,
                                                      int state) {
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, util::OVERLAY_STATE_CHANGED);
@@ -72,14 +72,9 @@
     AStatsEvent_writeString(statsEvent, packageName.c_str());
     // Missing field 3 - using_alert_window.
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -93,14 +88,9 @@
     AStatsEvent_writeString(statsEvent, packageName.c_str());
     AStatsEvent_writeInt32(statsEvent, true);       // using_alert_window
     AStatsEvent_writeString(statsEvent, "string");  // exclusive state: string instead of int
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 // END: build event functions.
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 7d765d3..ed3cf5b 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -507,23 +507,26 @@
 }
 // END: get primary key functions
 
-shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
-                                            int32_t value2) {
-    AStatsEvent* statsEvent = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(statsEvent, atomId);
-    AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
+void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids,
+                      const vector<string>& attributionTags) {
+    vector<const char*> cTags(attributionTags.size());
+    for (int i = 0; i < cTags.size(); i++) {
+        cTags[i] = attributionTags[i].c_str();
+    }
 
-    AStatsEvent_writeInt32(statsEvent, value1);
-    AStatsEvent_writeInt32(statsEvent, value2);
+    AStatsEvent_writeAttributionChain(statsEvent,
+                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
+                                      cTags.data(), attributionUids.size());
+}
+
+void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent) {
     AStatsEvent_build(statsEvent);
 
     size_t size;
     uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
     logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
 
-    return logEvent;
+    AStatsEvent_release(statsEvent);
 }
 
 void CreateTwoValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, int32_t value1,
@@ -534,31 +537,14 @@
 
     AStatsEvent_writeInt32(statsEvent, value1);
     AStatsEvent_writeInt32(statsEvent, value2);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
-shared_ptr<LogEvent> CreateThreeValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
-                                              int32_t value2, int32_t value3) {
-    AStatsEvent* statsEvent = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(statsEvent, atomId);
-    AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
-
-    AStatsEvent_writeInt32(statsEvent, value1);
-    AStatsEvent_writeInt32(statsEvent, value2);
-    AStatsEvent_writeInt32(statsEvent, value3);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
+                                            int32_t value2) {
     shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
-
+    CreateTwoValueLogEvent(logEvent.get(), atomId, eventTimeNs, value1, value2);
     return logEvent;
 }
 
@@ -571,29 +557,14 @@
     AStatsEvent_writeInt32(statsEvent, value1);
     AStatsEvent_writeInt32(statsEvent, value2);
     AStatsEvent_writeInt32(statsEvent, value3);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
-shared_ptr<LogEvent> CreateRepeatedValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value) {
-    AStatsEvent* statsEvent = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(statsEvent, atomId);
-    AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
-
-    AStatsEvent_writeInt32(statsEvent, value);
-    AStatsEvent_writeInt32(statsEvent, value);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+shared_ptr<LogEvent> CreateThreeValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
+                                              int32_t value2, int32_t value3) {
     shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
-
+    CreateThreeValueLogEvent(logEvent.get(), atomId, eventTimeNs, value1, value2, value3);
     return logEvent;
 }
 
@@ -605,26 +576,13 @@
 
     AStatsEvent_writeInt32(statsEvent, value);
     AStatsEvent_writeInt32(statsEvent, value);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
-shared_ptr<LogEvent> CreateNoValuesLogEvent(int atomId, int64_t eventTimeNs) {
-    AStatsEvent* statsEvent = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(statsEvent, atomId);
-    AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+shared_ptr<LogEvent> CreateRepeatedValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value) {
     shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
-
+    CreateRepeatedValueLogEvent(logEvent.get(), atomId, eventTimeNs, value);
     return logEvent;
 }
 
@@ -632,12 +590,14 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
+}
+
+shared_ptr<LogEvent> CreateNoValuesLogEvent(int atomId, int64_t eventTimeNs) {
+    shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
+    CreateNoValuesLogEvent(logEvent.get(), atomId, eventTimeNs);
+    return logEvent;
 }
 
 std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
@@ -645,16 +605,10 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, util::SCREEN_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -662,16 +616,10 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, util::BATTERY_SAVER_MODE_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
     AStatsEvent_writeInt32(statsEvent, BatterySaverModeStateChanged::ON);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -679,16 +627,10 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, util::BATTERY_SAVER_MODE_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
     AStatsEvent_writeInt32(statsEvent, BatterySaverModeStateChanged::OFF);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -696,16 +638,10 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, util::PLUGGED_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -713,16 +649,10 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, util::SCREEN_BRIGHTNESS_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
     AStatsEvent_writeInt32(statsEvent, level);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -733,24 +663,12 @@
     AStatsEvent_setAtomId(statsEvent, util::SCHEDULED_JOB_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
 
-    vector<const char*> cTags(attributionTags.size());
-    for (int i = 0; i < cTags.size(); i++) {
-        cTags[i] = attributionTags[i].c_str();
-    }
-
-    AStatsEvent_writeAttributionChain(statsEvent,
-                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
-                                      cTags.data(), attributionUids.size());
+    writeAttribution(statsEvent, attributionUids, attributionTags);
     AStatsEvent_writeString(statsEvent, jobName.c_str());
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -780,25 +698,13 @@
     AStatsEvent_setAtomId(statsEvent, util::WAKELOCK_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
 
-    vector<const char*> cTags(attributionTags.size());
-    for (int i = 0; i < cTags.size(); i++) {
-        cTags[i] = attributionTags[i].c_str();
-    }
-
-    AStatsEvent_writeAttributionChain(statsEvent,
-                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
-                                      cTags.data(), attributionUids.size());
+    writeAttribution(statsEvent, attributionUids, attributionTags);
     AStatsEvent_writeInt32(statsEvent, android::os::WakeLockLevelEnum::PARTIAL_WAKE_LOCK);
     AStatsEvent_writeString(statsEvent, wakelockName.c_str());
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -828,14 +734,9 @@
     AStatsEvent_writeString(statsEvent, "pkg_name");
     AStatsEvent_writeString(statsEvent, "class_name");
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -858,24 +759,12 @@
     AStatsEvent_setAtomId(statsEvent, util::SYNC_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
 
-    vector<const char*> cTags(attributionTags.size());
-    for (int i = 0; i < cTags.size(); i++) {
-        cTags[i] = attributionTags[i].c_str();
-    }
-
-    AStatsEvent_writeAttributionChain(statsEvent,
-                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
-                                      cTags.data(), attributionUids.size());
+    writeAttribution(statsEvent, attributionUids, attributionTags);
     AStatsEvent_writeString(statsEvent, name.c_str());
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -904,14 +793,9 @@
     AStatsEvent_writeInt32(statsEvent, uid);
     AStatsEvent_writeString(statsEvent, "");
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -928,14 +812,9 @@
     AStatsEvent_writeInt32(statsEvent, uid);
     AStatsEvent_writeString(statsEvent, "eventType");
     AStatsEvent_writeString(statsEvent, "processName");
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -948,14 +827,9 @@
     AStatsEvent_writeInt32(statsEvent, hostUid);
     AStatsEvent_writeInt32(statsEvent, isolatedUid);
     AStatsEvent_writeInt32(statsEvent, is_create);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -967,14 +841,9 @@
 
     AStatsEvent_writeInt32(statsEvent, uid);
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -988,26 +857,14 @@
     AStatsEvent_setAtomId(statsEvent, util::BLE_SCAN_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
 
-    vector<const char*> cTags(attributionTags.size());
-    for (int i = 0; i < cTags.size(); i++) {
-        cTags[i] = attributionTags[i].c_str();
-    }
-
-    AStatsEvent_writeAttributionChain(statsEvent,
-                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
-                                      cTags.data(), attributionUids.size());
+    writeAttribution(statsEvent, attributionUids, attributionTags);
     AStatsEvent_writeInt32(statsEvent, state);
     AStatsEvent_writeInt32(statsEvent, filtered);       // filtered
     AStatsEvent_writeInt32(statsEvent, firstMatch);     // first match
     AStatsEvent_writeInt32(statsEvent, opportunistic);  // opportunistic
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -1023,14 +880,9 @@
     AStatsEvent_writeString(statsEvent, packageName.c_str());
     AStatsEvent_writeInt32(statsEvent, usingAlertWindow);
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index f24705a..d6ea77eb 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -25,6 +25,7 @@
 #include "src/hash.h"
 #include "src/logd/LogEvent.h"
 #include "src/stats_log_util.h"
+#include "stats_event.h"
 #include "statslog_statsdtest.h"
 
 namespace android {
@@ -189,6 +190,12 @@
 void getPartialWakelockKey(int uid, HashableDimensionKey* key);
 // END: get primary key functions
 
+void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids,
+                      const vector<string>& attributionTags);
+
+// Builds statsEvent to get buffer that is parsed into logEvent then releases statsEvent.
+void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent);
+
 shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
                                             int32_t value2);
 
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 46b06fb..3a708a6 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1395,6 +1395,7 @@
     public static final String OPSTR_QUERY_ALL_PACKAGES = "android:query_all_packages";
     /** @hide Access all external storage */
     @SystemApi
+    @TestApi
     public static final String OPSTR_MANAGE_EXTERNAL_STORAGE =
             "android:manage_external_storage";
 
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 91a8572..e599a5c 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -1658,6 +1658,9 @@
         public final T getService(ContextImpl ctx) {
             final Object[] cache = ctx.mServiceCache;
             final int[] gates = ctx.mServiceInitializationStateArray;
+            boolean interrupted = false;
+
+            T ret = null;
 
             for (;;) {
                 boolean doInitialize = false;
@@ -1665,7 +1668,8 @@
                     // Return it if we already have a cached instance.
                     T service = (T) cache[mCacheIndex];
                     if (service != null || gates[mCacheIndex] == ContextImpl.STATE_NOT_FOUND) {
-                        return service;
+                        ret = service;
+                        break; // exit the for (;;)
                     }
 
                     // If we get here, there's no cached instance.
@@ -1708,24 +1712,33 @@
                             cache.notifyAll();
                         }
                     }
-                    return service;
+                    ret = service;
+                    break; // exit the for (;;)
                 }
                 // The other threads will wait for the first thread to call notifyAll(),
                 // and go back to the top and retry.
                 synchronized (cache) {
+                    // Repeat until the state becomes STATE_READY or STATE_NOT_FOUND.
+                    // We can't respond to interrupts here; just like we can't in the "doInitialize"
+                    // path, so we remember the interrupt state here and re-interrupt later.
                     while (gates[mCacheIndex] < ContextImpl.STATE_READY) {
                         try {
+                            // Clear the interrupt state.
+                            interrupted |= Thread.interrupted();
                             cache.wait();
                         } catch (InterruptedException e) {
                             // This shouldn't normally happen, but if someone interrupts the
                             // thread, it will.
-                            Slog.wtf(TAG, "getService() interrupted");
-                            Thread.currentThread().interrupt();
-                            return null;
+                            Slog.w(TAG, "getService() interrupted");
+                            interrupted = true;
                         }
                     }
                 }
             }
+            if (interrupted) {
+                Thread.currentThread().interrupt();
+            }
+            return ret;
         }
 
         public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index fc48e7f..f216db6 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -979,14 +979,17 @@
                 8, BLUETOOTH_GET_STATE_CACHE_PROPERTY) {
                 @Override
                 protected Integer recompute(Void query) {
-                    // This function must be called while holding the
-                    // mServiceLock, and with mService not null. The public
-                    // getState() method makes this guarantee.
                     try {
-                        return mService.getState();
+                        mServiceLock.readLock().lock();
+                        if (mService != null) {
+                            return mService.getState();
+                        }
                     } catch (RemoteException e) {
-                        throw e.rethrowFromSystemServer();
+                        Log.e(TAG, "", e);
+                    } finally {
+                        mServiceLock.readLock().unlock();
                     }
+                    return BluetoothAdapter.STATE_OFF;
                 }
             };
 
@@ -1013,24 +1016,7 @@
     @RequiresPermission(Manifest.permission.BLUETOOTH)
     @AdapterState
     public int getState() {
-        int state = BluetoothAdapter.STATE_OFF;
-
-        try {
-            mServiceLock.readLock().lock();
-            // The test for mService must either be outside the cache, or
-            // the cache must be invalidated when mService changes.
-            if (mService != null) {
-                state = mBluetoothGetStateCache.query(null);
-            }
-        } catch (RuntimeException e) {
-            if (e.getCause() instanceof RemoteException) {
-                Log.e(TAG, "", e.getCause());
-            } else {
-                throw e;
-            }
-        } finally {
-            mServiceLock.readLock().unlock();
-        }
+        int state = mBluetoothGetStateCache.query(null);
 
         // Consider all internal states as OFF
         if (state == BluetoothAdapter.STATE_BLE_ON || state == BluetoothAdapter.STATE_BLE_TURNING_ON
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index b7b3c4f..5d2c9d1 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -607,6 +607,9 @@
      *                             started.
      * @param pkgDataInfoMap Map from related package names to private data directory
      *                       volume UUID and inode number.
+     * @param whitelistedDataInfoMap Map from whitelisted package names to private data directory
+     *                       volume UUID and inode number.
+     * @param bindMountAppsData whether zygote needs to mount CE and DE data.
      * @param bindMountAppStorageDirs whether zygote needs to mount Android/obb and Android/data.
      * @param zygoteArgs Additional arguments to supply to the zygote process.
      * @return An object that describes the result of the attempt to start the process.
@@ -631,13 +634,17 @@
                                            @Nullable long[] disabledCompatChanges,
                                            @Nullable Map<String, Pair<String, Long>>
                                                    pkgDataInfoMap,
+                                           @Nullable Map<String, Pair<String, Long>>
+                                                   whitelistedDataInfoMap,
+                                           boolean bindMountAppsData,
                                            boolean bindMountAppStorageDirs,
                                            @Nullable String[] zygoteArgs) {
         return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,
                     runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, packageName,
                     zygotePolicyFlags, isTopApp, disabledCompatChanges,
-                    pkgDataInfoMap, bindMountAppStorageDirs, zygoteArgs);
+                    pkgDataInfoMap, whitelistedDataInfoMap, bindMountAppsData,
+                    bindMountAppStorageDirs, zygoteArgs);
     }
 
     /** @hide */
@@ -661,7 +668,8 @@
                     runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, packageName,
                     /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, /*isTopApp=*/ false,
-                disabledCompatChanges, /* pkgDataInfoMap */ null, false, zygoteArgs);
+                disabledCompatChanges, /* pkgDataInfoMap */ null,
+                /* whitelistedDataInfoMap */ null, false, false, zygoteArgs);
     }
 
     /**
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 5f3f14f..a4c99c0 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -333,6 +333,9 @@
      *                             started.
      * @param pkgDataInfoMap Map from related package names to private data directory
      *                       volume UUID and inode number.
+     * @param whitelistedDataInfoMap Map from whitelisted package names to private data directory
+     *                       volume UUID and inode number.
+     * @param bindMountAppsData whether zygote needs to mount CE and DE data.
      * @param bindMountAppStorageDirs whether zygote needs to mount Android/obb and Android/data.
      *
      * @param zygoteArgs Additional arguments to supply to the Zygote process.
@@ -355,6 +358,9 @@
                                                   @Nullable long[] disabledCompatChanges,
                                                   @Nullable Map<String, Pair<String, Long>>
                                                           pkgDataInfoMap,
+                                                  @Nullable Map<String, Pair<String, Long>>
+                                                          whitelistedDataInfoMap,
+                                                  boolean bindMountAppsData,
                                                   boolean bindMountAppStorageDirs,
                                                   @Nullable String[] zygoteArgs) {
         // TODO (chriswailes): Is there a better place to check this value?
@@ -367,7 +373,8 @@
                     runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false,
                     packageName, zygotePolicyFlags, isTopApp, disabledCompatChanges,
-                    pkgDataInfoMap, bindMountAppStorageDirs, zygoteArgs);
+                    pkgDataInfoMap, whitelistedDataInfoMap, bindMountAppsData,
+                    bindMountAppStorageDirs, zygoteArgs);
         } catch (ZygoteStartFailedEx ex) {
             Log.e(LOG_TAG,
                     "Starting VM process through Zygote failed");
@@ -608,6 +615,9 @@
      * @param disabledCompatChanges a list of disabled compat changes for the process being started.
      * @param pkgDataInfoMap Map from related package names to private data directory volume UUID
      *                       and inode number.
+     * @param whitelistedDataInfoMap Map from whitelisted package names to private data directory
+     *                       volume UUID and inode number.
+     * @param bindMountAppsData whether zygote needs to mount CE and DE data.
      * @param bindMountAppStorageDirs whether zygote needs to mount Android/obb and Android/data.
      * @param extraArgs Additional arguments to supply to the zygote process.
      * @return An object that describes the result of the attempt to start the process.
@@ -631,6 +641,9 @@
                                                       @Nullable long[] disabledCompatChanges,
                                                       @Nullable Map<String, Pair<String, Long>>
                                                               pkgDataInfoMap,
+                                                      @Nullable Map<String, Pair<String, Long>>
+                                                              whitelistedDataInfoMap,
+                                                      boolean bindMountAppsData,
                                                       boolean bindMountAppStorageDirs,
                                                       @Nullable String[] extraArgs)
                                                       throws ZygoteStartFailedEx {
@@ -728,11 +741,33 @@
             }
             argsForZygote.add(sb.toString());
         }
+        if (whitelistedDataInfoMap != null && whitelistedDataInfoMap.size() > 0) {
+            StringBuilder sb = new StringBuilder();
+            sb.append(Zygote.WHITELISTED_DATA_INFO_MAP);
+            sb.append("=");
+            boolean started = false;
+            for (Map.Entry<String, Pair<String, Long>> entry : whitelistedDataInfoMap.entrySet()) {
+                if (started) {
+                    sb.append(',');
+                }
+                started = true;
+                sb.append(entry.getKey());
+                sb.append(',');
+                sb.append(entry.getValue().first);
+                sb.append(',');
+                sb.append(entry.getValue().second);
+            }
+            argsForZygote.add(sb.toString());
+        }
 
         if (bindMountAppStorageDirs) {
             argsForZygote.add(Zygote.BIND_MOUNT_APP_STORAGE_DIRS);
         }
 
+        if (bindMountAppsData) {
+            argsForZygote.add(Zygote.BIND_MOUNT_APP_DATA_DIRS);
+        }
+
         if (disabledCompatChanges != null && disabledCompatChanges.length > 0) {
             StringBuilder sb = new StringBuilder();
             sb.append("--disabled-compat-changes=");
@@ -1291,6 +1326,7 @@
                     true /* startChildZygote */, null /* packageName */,
                     ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS /* zygotePolicyFlags */, false /* isTopApp */,
                     null /* disabledCompatChanges */, null /* pkgDataInfoMap */,
+                    null /* whitelistedDataInfoMap */, false /* bindMountAppsData*/,
                     /* bindMountAppStorageDirs */ false, extraArgs);
 
         } catch (ZygoteStartFailedEx ex) {
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 69bab4d..6cb9374 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -91,6 +91,8 @@
         if (mSourceControl == control) {
             return;
         }
+        SurfaceControl oldLeash = mSourceControl != null ? mSourceControl.getLeash() : null;
+
         final InsetsSourceControl lastControl = mSourceControl;
         mSourceControl = control;
 
@@ -116,6 +118,12 @@
                 // However make sure that the leash visibility is still up to date.
                 if (applyLocalVisibilityOverride()) {
                     mController.notifyVisibilityChanged();
+                }
+
+                // If we have a new leash, make sure visibility is up-to-date, even though we
+                // didn't want to run an animation above.
+                SurfaceControl newLeash = mSourceControl.getLeash();
+                if (oldLeash == null || newLeash == null || !oldLeash.isSameSurface(newLeash)) {
                     applyHiddenToControl();
                 }
             }
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index f3ec65f..e001b66 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -44,8 +44,7 @@
     public InsetsSourceControl(InsetsSourceControl other) {
         mType = other.mType;
         if (other.mLeash != null) {
-            mLeash = new SurfaceControl();
-            mLeash.copyFrom(other.mLeash);
+            mLeash = new SurfaceControl(other.mLeash);
         } else {
             mLeash = null;
         }
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index b5f9df7..ab65e3a 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -216,6 +216,7 @@
 
     private static native void nativeSetFrameRate(
             long transactionObj, long nativeObject, float frameRate, int compatibility);
+    private static native long nativeGetHandle(long nativeObject);
 
     private static native long nativeAcquireFrameRateFlexibilityToken();
     private static native void nativeReleaseFrameRateFlexibilityToken(long token);
@@ -226,6 +227,7 @@
      * @hide
      */
     public long mNativeObject;
+    private long mNativeHandle;
 
     // TODO: Move this to native.
     private final Object mSizeLock = new Object();
@@ -428,12 +430,13 @@
             mCloseGuard.open("release");
         }
         mNativeObject = nativeObject;
+        mNativeHandle = mNativeObject != 0 ? nativeGetHandle(nativeObject) : 0;
     }
 
     /**
      * @hide
      */
-    public void copyFrom(SurfaceControl other) {
+    public void copyFrom(@NonNull SurfaceControl other) {
         mName = other.mName;
         mWidth = other.mWidth;
         mHeight = other.mHeight;
@@ -853,23 +856,19 @@
             throw new OutOfResourcesException(
                     "Couldn't allocate SurfaceControl native object");
         }
-
+        mNativeHandle = nativeGetHandle(mNativeObject);
         mCloseGuard.open("release");
     }
 
-    /** This is a transfer constructor, useful for transferring a live SurfaceControl native
-     * object to another Java wrapper which could have some different behavior, e.g.
-     * event logging.
+    /**
+     * Copy constructor. Creates a new native object pointing to the same surface as {@code other}.
+     *
+     * @param other The object to copy the surface from.
      * @hide
      */
-    public SurfaceControl(SurfaceControl other) {
-        mName = other.mName;
-        mWidth = other.mWidth;
-        mHeight = other.mHeight;
-        mNativeObject = other.mNativeObject;
-        other.mCloseGuard.close();
-        other.mNativeObject = 0;
-        mCloseGuard.open("release");
+    @TestApi
+    public SurfaceControl(@NonNull SurfaceControl other) {
+        copyFrom(other);
     }
 
     private SurfaceControl(Parcel in) {
@@ -921,6 +920,18 @@
     }
 
     /**
+     * Checks whether two {@link SurfaceControl} objects represent the same surface.
+     *
+     * @param other The other object to check
+     * @return {@code true} if these two {@link SurfaceControl} objects represent the same surface.
+     * @hide
+     */
+    @TestApi
+    public boolean isSameSurface(@NonNull SurfaceControl other) {
+        return other.mNativeHandle == mNativeHandle;
+    }
+
+    /**
      * Write to a protocol buffer output stream. Protocol buffer message definition is at {@link
      * android.view.SurfaceControlProto}.
      *
@@ -977,6 +988,7 @@
         if (mNativeObject != 0) {
             nativeRelease(mNativeObject);
             mNativeObject = 0;
+            mNativeHandle = 0;
             mCloseGuard.close();
         }
     }
diff --git a/core/java/android/window/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java
index eee222b..6ae70b7 100644
--- a/core/java/android/window/DisplayAreaOrganizer.java
+++ b/core/java/android/window/DisplayAreaOrganizer.java
@@ -33,8 +33,8 @@
     public static final int FEATURE_SYSTEM_FIRST = 0;
     // The Root display area on a display
     public static final int FEATURE_ROOT = FEATURE_SYSTEM_FIRST;
-    // Display area hosting the task container.
-    public static final int FEATURE_TASK_CONTAINER = FEATURE_SYSTEM_FIRST + 1;
+    // Display area hosting the default task container.
+    public static final int FEATURE_DEFAULT_TASK_CONTAINER = FEATURE_SYSTEM_FIRST + 1;
     // Display area hosting non-activity window tokens.
     public static final int FEATURE_WINDOW_TOKENS = FEATURE_SYSTEM_FIRST + 2;
 
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index ff03f1a..505a05e 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -205,9 +205,15 @@
     /** List of packages with the same uid, and its app data info: volume uuid and inode. */
     public static final String PKG_DATA_INFO_MAP = "--pkg-data-info-map";
 
+    /** List of whitelisted packages and its app data info: volume uuid and inode. */
+    public static final String WHITELISTED_DATA_INFO_MAP = "--whitelisted-data-info-map";
+
     /** Bind mount app storage dirs to lower fs not via fuse */
     public static final String BIND_MOUNT_APP_STORAGE_DIRS = "--bind-mount-storage-dirs";
 
+    /** Bind mount app storage dirs to lower fs not via fuse */
+    public static final String BIND_MOUNT_APP_DATA_DIRS = "--bind-mount-data-dirs";
+
     /**
      * An extraArg passed when a zygote process is forking a child-zygote, specifying a name
      * in the abstract socket namespace. This socket name is what the new child zygote
@@ -313,6 +319,8 @@
      * @param isTopApp true if the process is for top (high priority) application.
      * @param pkgDataInfoList A list that stores related packages and its app data
      * info: volume uuid and inode.
+     * @param whitelistedDataInfoList Like pkgDataInfoList, but it's for whitelisted apps.
+     * @param bindMountAppDataDirs  True if the zygote needs to mount data dirs.
      * @param bindMountAppStorageDirs  True if the zygote needs to mount storage dirs.
      *
      * @return 0 if this is the child, pid of the child
@@ -321,13 +329,15 @@
     static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
             int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
             int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
-            boolean isTopApp, String[] pkgDataInfoList, boolean bindMountAppStorageDirs) {
+            boolean isTopApp, String[] pkgDataInfoList, String[] whitelistedDataInfoList,
+            boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs) {
         ZygoteHooks.preFork();
 
         int pid = nativeForkAndSpecialize(
                 uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
                 fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp,
-                pkgDataInfoList, bindMountAppStorageDirs);
+                pkgDataInfoList, whitelistedDataInfoList, bindMountAppDataDirs,
+                bindMountAppStorageDirs);
         if (pid == 0) {
             // Note that this event ends at the end of handleChildProc,
             Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
@@ -344,6 +354,7 @@
             int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
             int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet,
             String appDataDir, boolean isTopApp, String[] pkgDataInfoList,
+            String[] whitelistedDataInfoList, boolean bindMountAppDataDirs,
             boolean bindMountAppStorageDirs);
 
     /**
@@ -371,15 +382,19 @@
      * volume uuid and CE dir inode. For example, pkgDataInfoList = [app_a_pkg_name,
      * app_a_data_volume_uuid, app_a_ce_inode, app_b_pkg_name, app_b_data_volume_uuid,
      * app_b_ce_inode, ...];
+     * @param whitelistedDataInfoList Like pkgDataInfoList, but it's for whitelisted apps.
+     * @param bindMountAppDataDirs  True if the zygote needs to mount data dirs.
      * @param bindMountAppStorageDirs  True if the zygote needs to mount storage dirs.
      */
     private static void specializeAppProcess(int uid, int gid, int[] gids, int runtimeFlags,
             int[][] rlimits, int mountExternal, String seInfo, String niceName,
             boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
-            String[] pkgDataInfoList, boolean bindMountAppStorageDirs) {
+            String[] pkgDataInfoList, String[] whitelistedDataInfoList,
+            boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs) {
         nativeSpecializeAppProcess(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo,
                 niceName, startChildZygote, instructionSet, appDataDir, isTopApp,
-                pkgDataInfoList, bindMountAppStorageDirs);
+                pkgDataInfoList, whitelistedDataInfoList,
+                bindMountAppDataDirs, bindMountAppStorageDirs);
 
         // Note that this event ends at the end of handleChildProc.
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
@@ -399,7 +414,8 @@
     private static native void nativeSpecializeAppProcess(int uid, int gid, int[] gids,
             int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
             boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
-            String[] pkgDataInfoList, boolean bindMountAppStorageDirs);
+            String[] pkgDataInfoList, String[] whitelistedDataInfoList,
+            boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs);
 
     /**
      * Called to do any initialization before starting an application.
@@ -724,7 +740,8 @@
                                  args.mRuntimeFlags, rlimits, args.mMountExternal,
                                  args.mSeInfo, args.mNiceName, args.mStartChildZygote,
                                  args.mInstructionSet, args.mAppDataDir, args.mIsTopApp,
-                                 args.mPkgDataInfoList, args.mBindMountAppStorageDirs);
+                                 args.mPkgDataInfoList, args.mWhitelistedDataInfoList,
+                                 args.mBindMountAppDataDirs, args.mBindMountAppStorageDirs);
 
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
 
@@ -1060,4 +1077,11 @@
      */
     @FastNative
     public static native int nativeParseSigChld(byte[] in, int length, int[] out);
+
+    /**
+     * Returns whether the kernel supports tagged pointers. Present in the
+     * Android Common Kernel from 4.14 and up. By default, you should prefer
+     * fully-feature Memory Tagging, rather than the static Tagged Pointers.
+     */
+    public static native boolean nativeSupportsTaggedPointers();
 }
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index 1a63765..94c1f71 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -227,11 +227,22 @@
     String[] mPkgDataInfoList;
 
     /**
+     * A list that stores all whitelisted app data info: volume uuid and inode.
+     * Null if it does need to do app data isolation.
+     */
+    String[] mWhitelistedDataInfoList;
+
+    /**
      * @see Zygote#BIND_MOUNT_APP_STORAGE_DIRS
      */
     boolean mBindMountAppStorageDirs;
 
     /**
+     * @see Zygote#BIND_MOUNT_APP_DATA_DIRS
+     */
+    boolean mBindMountAppDataDirs;
+
+    /**
      * Constructs instance and parses args
      *
      * @param args zygote command-line args
@@ -452,8 +463,12 @@
                 }
             } else if (arg.startsWith(Zygote.PKG_DATA_INFO_MAP)) {
                 mPkgDataInfoList = getAssignmentList(arg);
+            } else if (arg.startsWith(Zygote.WHITELISTED_DATA_INFO_MAP)) {
+                mWhitelistedDataInfoList = getAssignmentList(arg);
             } else if (arg.equals(Zygote.BIND_MOUNT_APP_STORAGE_DIRS)) {
                 mBindMountAppStorageDirs = true;
+            } else if (arg.equals(Zygote.BIND_MOUNT_APP_DATA_DIRS)) {
+                mBindMountAppDataDirs = true;
             } else {
                 break;
             }
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index bc8dfd4..e6a3029 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -258,7 +258,8 @@
                 parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo,
                 parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
                 parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mIsTopApp,
-                parsedArgs.mPkgDataInfoList, parsedArgs.mBindMountAppStorageDirs);
+                parsedArgs.mPkgDataInfoList, parsedArgs.mWhitelistedDataInfoList,
+                parsedArgs.mBindMountAppDataDirs, parsedArgs.mBindMountAppStorageDirs);
 
         try {
             if (pid == 0) {
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index ec1f516..c2b13c9 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -757,9 +757,11 @@
             Zygote.applyDebuggerSystemProperty(parsedArgs);
             Zygote.applyInvokeWithSystemProperty(parsedArgs);
 
-            /* Enable pointer tagging in the system server unconditionally. Hardware support for
-             * this is present in all ARMv8 CPUs; this flag has no effect on other platforms. */
-            parsedArgs.mRuntimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI;
+            if (Zygote.nativeSupportsTaggedPointers()) {
+                /* Enable pointer tagging in the system server. Hardware support for this is present
+                 * in all ARMv8 CPUs. */
+                parsedArgs.mRuntimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI;
+            }
 
             /* Enable gwp-asan on the system server with a small probability. This is the same
              * policy as applied to native processes and system apps. */
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index b11c4c9..1cfa12d 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1430,6 +1430,12 @@
 
     client->setGlobalShadowSettings(ambientColor, spotColor, lightPosY, lightPosZ, lightRadius);
 }
+
+static jlong nativeGetHandle(JNIEnv* env, jclass clazz, jlong nativeObject) {
+    SurfaceControl *surfaceControl = reinterpret_cast<SurfaceControl*>(nativeObject);
+    return reinterpret_cast<jlong>(surfaceControl->getHandle().get());
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod sSurfaceControlMethods[] = {
@@ -1606,6 +1612,8 @@
             (void*)nativeMirrorSurface },
     {"nativeSetGlobalShadowSettings", "([F[FFFF)V",
             (void*)nativeSetGlobalShadowSettings },
+    {"nativeGetHandle", "(J)J",
+            (void*)nativeGetHandle },
 };
 
 int register_android_view_SurfaceControl(JNIEnv* env)
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index ea3c0fa..4b30359 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -110,7 +110,6 @@
 using android::base::StringPrintf;
 using android::base::WriteStringToFile;
 using android::base::GetBoolProperty;
-using android::base::GetProperty;
 
 #define CREATE_ERROR(...) StringPrintf("%s:%d: ", __FILE__, __LINE__). \
                               append(StringPrintf(__VA_ARGS__))
@@ -170,18 +169,6 @@
 
 static constexpr int DEFAULT_DATA_DIR_PERMISSION = 0751;
 
-/**
- * Property to control if app data isolation is enabled.
- */
-static const std::string ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY =
-    "persist.zygote.app_data_isolation";
-
-/**
- * Property to enable app data isolation for sdcard obb or data in vold.
- */
-static const std::string ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY =
-    "persist.sys.vold_app_data_isolation_enabled";
-
 static constexpr const uint64_t UPPER_HALF_WORD_MASK = 0xFFFF'FFFF'0000'0000;
 static constexpr const uint64_t LOWER_HALF_WORD_MASK = 0x0000'0000'FFFF'FFFF;
 
@@ -1319,20 +1306,13 @@
  * be decrypted after storage is decrypted.
  *
  */
-static void isolateAppData(JNIEnv* env, jobjectArray pkg_data_info_list,
-    uid_t uid, const char* process_name, jstring managed_nice_name,
-    fail_fn_t fail_fn) {
+static void isolateAppData(JNIEnv* env, const std::vector<std::string>& merged_data_info_list,
+    uid_t uid, const char* process_name,
+    jstring managed_nice_name, fail_fn_t fail_fn) {
 
   const userid_t userId = multiuser_get_user_id(uid);
 
-  auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
-
-  int size = (pkg_data_info_list != nullptr) ? env->GetArrayLength(pkg_data_info_list) : 0;
-  // Size should be a multiple of 3, as it contains list of <package_name, volume_uuid, inode>
-  if ((size % 3) != 0) {
-    fail_fn(CREATE_ERROR("Wrong pkg_inode_list size %d", size));
-  }
-  ensureInAppMountNamespace(fail_fn);
+  int size = merged_data_info_list.size();
 
   // Mount tmpfs on all possible data directories, so app no longer see the original apps data.
   char internalCePath[PATH_MAX];
@@ -1377,14 +1357,10 @@
   bool legacySymlinkCreated = false;
 
   for (int i = 0; i < size; i += 3) {
-    jstring package_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i));
-    std::string packageName = extract_fn(package_str).value();
+    std::string const & packageName = merged_data_info_list[i];
+    std::string const & volUuid  = merged_data_info_list[i + 1];
+    std::string const & inode = merged_data_info_list[i + 2];
 
-    jstring vol_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i + 1));
-    std::string volUuid = extract_fn(vol_str).value();
-
-    jstring inode_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i + 2));
-    std::string inode = extract_fn(inode_str).value();
     std::string::size_type sz;
     long long ceDataInode = std::stoll(inode, &sz);
 
@@ -1482,6 +1458,48 @@
   freecon(dataDataContext);
 }
 
+static void insertPackagesToMergedList(JNIEnv* env,
+  std::vector<std::string>& merged_data_info_list,
+  jobjectArray data_info_list, const char* process_name,
+  jstring managed_nice_name, fail_fn_t fail_fn) {
+
+  auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
+
+  int size = (data_info_list != nullptr) ? env->GetArrayLength(data_info_list) : 0;
+  // Size should be a multiple of 3, as it contains list of <package_name, volume_uuid, inode>
+  if ((size % 3) != 0) {
+    fail_fn(CREATE_ERROR("Wrong data_info_list size %d", size));
+  }
+
+  for (int i = 0; i < size; i += 3) {
+    jstring package_str = (jstring) (env->GetObjectArrayElement(data_info_list, i));
+    std::string packageName = extract_fn(package_str).value();
+    merged_data_info_list.push_back(packageName);
+
+    jstring vol_str = (jstring) (env->GetObjectArrayElement(data_info_list, i + 1));
+    std::string volUuid = extract_fn(vol_str).value();
+    merged_data_info_list.push_back(volUuid);
+
+    jstring inode_str = (jstring) (env->GetObjectArrayElement(data_info_list, i + 2));
+    std::string inode = extract_fn(inode_str).value();
+    merged_data_info_list.push_back(inode);
+  }
+}
+
+static void isolateAppData(JNIEnv* env, jobjectArray pkg_data_info_list,
+    jobjectArray whitelisted_data_info_list, uid_t uid, const char* process_name,
+    jstring managed_nice_name, fail_fn_t fail_fn) {
+
+  ensureInAppMountNamespace(fail_fn);
+  std::vector<std::string> merged_data_info_list;
+  insertPackagesToMergedList(env, merged_data_info_list, pkg_data_info_list,
+          process_name, managed_nice_name, fail_fn);
+  insertPackagesToMergedList(env, merged_data_info_list, whitelisted_data_info_list,
+          process_name, managed_nice_name, fail_fn);
+
+  isolateAppData(env, merged_data_info_list, uid, process_name, managed_nice_name, fail_fn);
+}
+
 /**
  * Like isolateAppData(), isolate jit profile directories, so apps don't see what
  * other apps are installed by reading content inside /data/misc/profiles/cur.
@@ -1594,7 +1612,9 @@
                              jstring managed_nice_name, bool is_system_server,
                              bool is_child_zygote, jstring managed_instruction_set,
                              jstring managed_app_data_dir, bool is_top_app,
-                             jobjectArray pkg_data_info_list, bool mount_storage_dirs) {
+                             jobjectArray pkg_data_info_list,
+                             jobjectArray whitelisted_data_info_list,
+                             bool mount_data_dirs, bool mount_storage_dirs) {
   const char* process_name = is_system_server ? "system_server" : "zygote";
   auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1);
   auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
@@ -1628,9 +1648,9 @@
   // give a null in same_uid_pkgs and private_volumes so they don't need app data isolation.
   // Isolated process / webview / app zygote should be gated by SELinux and file permission
   // so they can't even traverse CE / DE directories.
-  if (pkg_data_info_list != nullptr
-      && GetBoolProperty(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true)) {
-    isolateAppData(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn);
+  if (mount_data_dirs) {
+    isolateAppData(env, pkg_data_info_list, whitelisted_data_info_list,
+            uid, process_name, managed_nice_name, fail_fn);
     isolateJitProfile(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn);
   }
   if ((mount_external != MOUNT_EXTERNAL_INSTALLER) && mount_storage_dirs) {
@@ -2003,7 +2023,8 @@
         jint mount_external, jstring se_info, jstring nice_name,
         jintArray managed_fds_to_close, jintArray managed_fds_to_ignore, jboolean is_child_zygote,
         jstring instruction_set, jstring app_data_dir, jboolean is_top_app,
-        jobjectArray pkg_data_info_list, jboolean mount_storage_dirs) {
+        jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list,
+        jboolean mount_data_dirs, jboolean mount_storage_dirs) {
     jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
 
     if (UNLIKELY(managed_fds_to_close == nullptr)) {
@@ -2041,6 +2062,8 @@
                        mount_external, se_info, nice_name, false,
                        is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
                        is_top_app == JNI_TRUE, pkg_data_info_list,
+                       whitelisted_data_info_list,
+                       mount_data_dirs == JNI_TRUE,
                        mount_storage_dirs == JNI_TRUE);
     }
     return pid;
@@ -2076,7 +2099,8 @@
                        permitted_capabilities, effective_capabilities,
                        MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true,
                        false, nullptr, nullptr, /* is_top_app= */ false,
-                       /* pkg_data_info_list */ nullptr, false);
+                       /* pkg_data_info_list */ nullptr,
+                       /* whitelisted_data_info_list */ nullptr, false, false);
   } else if (pid > 0) {
       // The zygote process checks whether the child process has died or not.
       ALOGI("System server process %d has been created", pid);
@@ -2206,15 +2230,16 @@
     jint runtime_flags, jobjectArray rlimits,
     jint mount_external, jstring se_info, jstring nice_name,
     jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app,
-    jobjectArray pkg_data_info_list, jboolean mount_storage_dirs) {
+    jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list,
+    jboolean mount_data_dirs, jboolean mount_storage_dirs) {
   jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
 
   SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
                    capabilities, capabilities,
                    mount_external, se_info, nice_name, false,
                    is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
-                   is_top_app == JNI_TRUE, pkg_data_info_list,
-                   mount_storage_dirs == JNI_TRUE);
+                   is_top_app == JNI_TRUE, pkg_data_info_list, whitelisted_data_info_list,
+                   mount_data_dirs == JNI_TRUE, mount_storage_dirs == JNI_TRUE);
 }
 
 /**
@@ -2405,10 +2430,19 @@
     return -1;
 }
 
+static jboolean com_android_internal_os_Zygote_nativeSupportsTaggedPointers(JNIEnv* env, jclass) {
+#ifdef __aarch64__
+  int res = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
+  return res >= 0 && res & PR_TAGGED_ADDR_ENABLE;
+#else
+  return false;
+#endif
+}
+
 static const JNINativeMethod gMethods[] = {
         {"nativeForkAndSpecialize",
          "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/"
-         "String;Z[Ljava/lang/String;Z)I",
+         "String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)I",
          (void*)com_android_internal_os_Zygote_nativeForkAndSpecialize},
         {"nativeForkSystemServer", "(II[II[[IJJ)I",
          (void*)com_android_internal_os_Zygote_nativeForkSystemServer},
@@ -2421,7 +2455,7 @@
         {"nativeForkUsap", "(II[IZ)I", (void*)com_android_internal_os_Zygote_nativeForkUsap},
         {"nativeSpecializeAppProcess",
          "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/"
-         "String;Z[Ljava/lang/String;Z)V",
+         "String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)V",
          (void*)com_android_internal_os_Zygote_nativeSpecializeAppProcess},
         {"nativeInitNativeState", "(Z)V",
          (void*)com_android_internal_os_Zygote_nativeInitNativeState},
@@ -2440,6 +2474,8 @@
          (void*)com_android_internal_os_Zygote_nativeBoostUsapPriority},
         {"nativeParseSigChld", "([BI[I)I",
          (void*)com_android_internal_os_Zygote_nativeParseSigChld},
+        {"nativeSupportsTaggedPointers", "()Z",
+         (void*)com_android_internal_os_Zygote_nativeSupportsTaggedPointers},
 };
 
 int register_com_android_internal_os_Zygote(JNIEnv* env) {
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index cbb379b..d432dda 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -696,8 +696,7 @@
 
         // Simulate binder behavior by copying SurfaceControl. Otherwise, InsetsController will
         // attempt to release mLeash directly.
-        SurfaceControl copy = new SurfaceControl();
-        copy.copyFrom(mLeash);
+        SurfaceControl copy = new SurfaceControl(mLeash);
         return new InsetsSourceControl(type, copy, new Point());
     }
 
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 59bdf3d..0389639 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -137,6 +137,7 @@
         <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
         <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
         <permission name="android.permission.PACKAGE_USAGE_STATS" />
+        <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.phone">
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 4150926..75ea0cb 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -47,10 +47,10 @@
     Location getLastLocation(in LocationRequest request, String packageName, String featureId);
     boolean getCurrentLocation(in LocationRequest request,
             in ICancellationSignal cancellationSignal, in ILocationListener listener,
-            String packageName, String featureId);
+            String packageName, String featureId, String listenerId);
 
     void requestLocationUpdates(in LocationRequest request, in ILocationListener listener,
-            in PendingIntent intent, String packageName, String featureId);
+            in PendingIntent intent, String packageName, String featureId, String listenerId);
     void removeUpdates(in ILocationListener listener, in PendingIntent intent);
 
     void requestGeofence(in LocationRequest request, in Geofence geofence,
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index fcbd3e5..d1b41df 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -718,7 +718,7 @@
             currentLocationRequest.setExpireIn(GET_CURRENT_LOCATION_MAX_TIMEOUT_MS);
         }
 
-        GetCurrentLocationTransport listenerTransport = new GetCurrentLocationTransport(executor,
+        GetCurrentLocationTransport transport = new GetCurrentLocationTransport(executor,
                 consumer);
 
         if (cancellationSignal != null) {
@@ -729,14 +729,15 @@
 
         try {
             if (mService.getCurrentLocation(currentLocationRequest, remoteCancellationSignal,
-                    listenerTransport, mContext.getPackageName(), mContext.getAttributionTag())) {
-                listenerTransport.register(mContext.getSystemService(AlarmManager.class),
+                    transport, mContext.getPackageName(), mContext.getAttributionTag(),
+                    transport.getListenerId())) {
+                transport.register(mContext.getSystemService(AlarmManager.class),
                         remoteCancellationSignal);
                 if (cancellationSignal != null) {
-                    cancellationSignal.setOnCancelListener(listenerTransport::cancel);
+                    cancellationSignal.setOnCancelListener(transport::cancel);
                 }
             } else {
-                listenerTransport.fail();
+                transport.fail();
             }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -1175,7 +1176,8 @@
             boolean registered = false;
             try {
                 mService.requestLocationUpdates(locationRequest, transport, null,
-                        mContext.getPackageName(), mContext.getAttributionTag());
+                        mContext.getPackageName(), mContext.getAttributionTag(),
+                        transport.getListenerId());
                 registered = true;
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
@@ -1220,7 +1222,7 @@
 
         try {
             mService.requestLocationUpdates(locationRequest, null, pendingIntent,
-                    mContext.getPackageName(), mContext.getAttributionTag());
+                    mContext.getPackageName(), mContext.getAttributionTag(), null);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2558,6 +2560,10 @@
             mRemoteCancellationSignal = null;
         }
 
+        public String getListenerId() {
+            return mConsumer.getClass().getName() + "@" + System.identityHashCode(mConsumer);
+        }
+
         public synchronized void register(AlarmManager alarmManager,
                 ICancellationSignal remoteCancellationSignal) {
             if (mConsumer == null) {
@@ -2683,6 +2689,10 @@
             return mListener;
         }
 
+        public String getListenerId() {
+            return mListener.getClass().getName() + "@" + System.identityHashCode(mListener);
+        }
+
         public void register(@NonNull Executor executor) {
             Preconditions.checkArgument(executor != null, "invalid null executor");
             mExecutor = executor;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
index 8aa0aec..a53bc9f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
@@ -102,12 +102,11 @@
         // Turn off divider
         view.findViewById(R.id.two_target_divider).setVisibility(View.INVISIBLE);
 
-        // Enable the icon button when this Entry is a canManageSubscription entry.
+        // Enable the icon button when the help string in this WifiEntry is not null.
         final ImageButton imageButton = (ImageButton) view.findViewById(R.id.icon_button);
         final ImageView frictionImageView = (ImageView) view.findViewById(
                 R.id.friction_icon);
-        if (mWifiEntry.canManageSubscription() && !mWifiEntry.isSaved()
-                && !mWifiEntry.isSubscription()
+        if (mWifiEntry.getHelpUriString() != null
                 && mWifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_DISCONNECTED) {
             final Drawable drawablehelp = getDrawable(R.drawable.ic_help);
             drawablehelp.setTintList(
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java
index a9f31ce..46e699d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java
@@ -64,7 +64,7 @@
 
     private static final String MOCK_TITLE = "title";
     private static final String MOCK_SUMMARY = "summary";
-
+    private static final String FAKE_URI_STRING = "fakeuri";
 
     @Before
     public void setUp() {
@@ -155,22 +155,23 @@
     }
 
     @Test
-    public void canManageSubscription_shouldSetImageButtonVisible() {
-        when(mMockWifiEntry.canManageSubscription()).thenReturn(true);
+    public void notNull_whenGetHelpUriString_shouldSetImageButtonVisible() {
+        when(mMockWifiEntry.getHelpUriString()).thenReturn(FAKE_URI_STRING);
         final WifiEntryPreference pref =
                 new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector);
         final LayoutInflater inflater = LayoutInflater.from(mContext);
         final View view = inflater.inflate(pref.getLayoutResource(), new LinearLayout(mContext),
                 false);
         final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(view);
+
         pref.onBindViewHolder(holder);
 
         assertThat(view.findViewById(R.id.icon_button).getVisibility()).isEqualTo(View.VISIBLE);
     }
 
     @Test
-    public void helpButton_whenCanManageSubscription_shouldSetCorrectContentDescription() {
-        when(mMockWifiEntry.canManageSubscription()).thenReturn(true);
+    public void helpButton_whenGetHelpUriStringNotNull_shouldSetCorrectContentDescription() {
+        when(mMockWifiEntry.getHelpUriString()).thenReturn(FAKE_URI_STRING);
         final WifiEntryPreference pref =
                 new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector);
         final LayoutInflater inflater = LayoutInflater.from(mContext);
diff --git a/packages/SystemUI/res/layout/qs_media_panel.xml b/packages/SystemUI/res/layout/qs_media_panel.xml
index fc3bf94..e5ac5f8 100644
--- a/packages/SystemUI/res/layout/qs_media_panel.xml
+++ b/packages/SystemUI/res/layout/qs_media_panel.xml
@@ -23,7 +23,8 @@
     android:layout_height="match_parent"
     android:orientation="vertical"
     android:gravity="center_horizontal|fill_vertical"
-    android:padding="16dp"
+    android:paddingTop="@dimen/qs_media_panel_outer_padding"
+    android:paddingBottom="@dimen/qs_media_panel_outer_padding"
     android:background="@drawable/qs_media_background"
     >
 
@@ -42,7 +43,9 @@
             android:orientation="horizontal"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_marginBottom="16dp"
+            android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
+            android:paddingStart="@dimen/qs_media_panel_outer_padding"
+            android:paddingEnd="16dp"
         >
 
             <ImageView
@@ -139,6 +142,7 @@
         <!-- Seek Bar -->
         <SeekBar
             android:id="@+id/media_progress_bar"
+            style="@android:style/Widget.ProgressBar.Horizontal"
             android:clickable="true"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
@@ -154,6 +158,9 @@
             android:id="@+id/notification_media_progress_time"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:paddingStart="@dimen/qs_media_panel_outer_padding"
+            android:paddingEnd="@dimen/qs_media_panel_outer_padding"
+            android:layout_marginBottom="10dp"
             android:layout_gravity="center"
             >
             <!-- width is set to "match_parent" to avoid extra layout calls -->
@@ -184,6 +191,8 @@
             android:layoutDirection="ltr"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:paddingStart="@dimen/qs_media_panel_outer_padding"
+            android:paddingEnd="@dimen/qs_media_panel_outer_padding"
             android:gravity="center"
             >
             <ImageButton
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index bce5fac..344479f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1211,6 +1211,7 @@
     <!-- Size of media cards in the QSPanel carousel -->
     <dimen name="qs_media_width">350dp</dimen>
     <dimen name="qs_media_padding">8dp</dimen>
+    <dimen name="qs_media_panel_outer_padding">16dp</dimen>
     <dimen name="qs_media_corner_radius">10dp</dimen>
     <dimen name="qs_media_album_size">72dp</dimen>
     <dimen name="qs_seamless_icon_size">20dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 23fa645..5442299 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -120,7 +120,7 @@
         private void init(DozeParameters dozeParameters) {
             mIsHighEndGfx = ActivityManager.isHighEndGfx();
             mDisplayNeedsBlanking = dozeParameters.getDisplayNeedsBlanking();
-            mNeedTransition = mIsHighEndGfx && !mDisplayNeedsBlanking;
+            mNeedTransition = false;
 
             // We will preserve EGL context when we are in lock screen or aod
             // to avoid janking in following transition, we need to release when back to home.
@@ -137,7 +137,7 @@
             mRenderer = getRendererInstance();
             getDisplayContext().getDisplay().getDisplayInfo(mDisplayInfo);
             setFixedSizeAllowed(true);
-            setOffsetNotificationsEnabled(true);
+            setOffsetNotificationsEnabled(mNeedTransition);
             updateSurfaceSize();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 9d885fd..99e5eb6 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -719,13 +719,6 @@
     }
 
     /**
-     * Tell the stack of bubbles to expand.
-     */
-    public void expandStack() {
-        mBubbleData.setExpanded(true);
-    }
-
-    /**
      * Tell the stack of bubbles to collapse.
      */
     public void collapseStack() {
@@ -753,12 +746,6 @@
         return (isSummary && isSuppressedSummary) || isBubbleAndSuppressed;
     }
 
-    @VisibleForTesting
-    void selectBubble(String key) {
-        Bubble bubble = mBubbleData.getBubbleWithKey(key);
-        mBubbleData.setSelectedBubble(bubble);
-    }
-
     void promoteBubbleFromOverflow(Bubble bubble) {
         bubble.setInflateSynchronously(mInflateSynchronously);
         mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory);
@@ -778,13 +765,6 @@
     }
 
     /**
-     * Tell the stack of bubbles to be dismissed, this will remove all of the bubbles in the stack.
-     */
-    void dismissStack(@DismissReason int reason) {
-        mBubbleData.dismissAll(reason);
-    }
-
-    /**
      * Directs a back gesture at the bubble stack. When opened, the current expanded bubble
      * is forwarded a back key down/up pair.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index be9cd5f..4c149dd 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -28,6 +28,7 @@
 import android.service.notification.NotificationListenerService;
 import android.util.Log;
 import android.util.Pair;
+import android.view.View;
 
 import androidx.annotation.Nullable;
 
@@ -751,6 +752,7 @@
     }
 
     @VisibleForTesting(visibility = PRIVATE)
+    @Nullable
     Bubble getBubbleWithKey(String key) {
         for (int i = 0; i < mBubbles.size(); i++) {
             Bubble bubble = mBubbles.get(i);
@@ -761,6 +763,17 @@
         return null;
     }
 
+    @Nullable
+    Bubble getBubbleWithView(View view) {
+        for (int i = 0; i < mBubbles.size(); i++) {
+            Bubble bubble = mBubbles.get(i);
+            if (bubble.getIconView() != null && bubble.getIconView().equals(view)) {
+                return bubble;
+            }
+        }
+        return null;
+    }
+
     @VisibleForTesting(visibility = PRIVATE)
     Bubble getOverflowBubbleWithKey(String key) {
         for (int i = 0; i < mOverflowBubbles.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 044feaa..644e54f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -30,6 +30,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
 import android.app.Notification;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -43,6 +44,7 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.graphics.Region;
 import android.os.Bundle;
 import android.os.Vibrator;
 import android.util.Log;
@@ -83,6 +85,7 @@
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.util.DismissCircleView;
 import com.android.systemui.util.FloatingContentCoordinator;
+import com.android.systemui.util.RelativeTouchListener;
 import com.android.systemui.util.animation.PhysicsAnimator;
 import com.android.systemui.util.magnetictarget.MagnetizedObject;
 
@@ -239,7 +242,6 @@
         mExpandedAnimationController.dump(fd, pw, args);
     }
 
-    private BubbleTouchHandler mTouchHandler;
     private BubbleController.BubbleExpandListener mExpandListener;
     private SysUiState mSysUiState;
 
@@ -296,7 +298,7 @@
 
                 @Override
                 public void setValue(Object o, float v) {
-                    onFlyoutDragged(v);
+                    setFlyoutStateForDragLength(v);
                 }
             };
 
@@ -337,13 +339,6 @@
     private MagnetizedObject<?> mMagnetizedObject;
 
     /**
-     * The action to run when the magnetized object is released in the dismiss target.
-     *
-     * This will actually perform the dismissal of either the stack or an individual bubble.
-     */
-    private Runnable mReleasedInDismissTargetAction;
-
-    /**
      * The MagneticTarget instance for our circular dismiss view. This is added to the
      * MagnetizedObject instances for the stack and any dragged-out bubbles.
      */
@@ -377,7 +372,7 @@
                 public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
                     mExpandedAnimationController.dismissDraggedOutBubble(
                             mExpandedAnimationController.getDraggedOutBubble(),
-                            mReleasedInDismissTargetAction);
+                            BubbleStackView.this::dismissMagnetizedObject);
                     hideDismissTarget();
                 }
             };
@@ -410,7 +405,7 @@
                     mStackAnimationController.implodeStack(
                             () -> {
                                 resetDesaturationAndDarken();
-                                mReleasedInDismissTargetAction.run();
+                                dismissMagnetizedObject();
                             }
                     );
 
@@ -418,6 +413,197 @@
                 }
             };
 
+    /**
+     * Click listener set on each bubble view. When collapsed, clicking a bubble expands the stack.
+     * When expanded, clicking a bubble either expands that bubble, or collapses the stack.
+     */
+    private OnClickListener mBubbleClickListener = new OnClickListener() {
+        @Override
+        public void onClick(View view) {
+            final Bubble clickedBubble = mBubbleData.getBubbleWithView(view);
+
+            // If the bubble has since left us, ignore the click.
+            if (clickedBubble == null) {
+                return;
+            }
+
+            final boolean clickedBubbleIsCurrentlyExpandedBubble =
+                    clickedBubble.getKey().equals(mExpandedBubble.getKey());
+
+            if (isExpanded() && !clickedBubbleIsCurrentlyExpandedBubble) {
+                if (clickedBubble != mBubbleData.getSelectedBubble()) {
+                    // Select the clicked bubble.
+                    mBubbleData.setSelectedBubble(clickedBubble);
+                } else {
+                    // If the clicked bubble is the selected bubble (but not the expanded bubble),
+                    // that means overflow was previously expanded. Set the selected bubble
+                    // internally without going through BubbleData (which would ignore it since it's
+                    // already selected).
+                    setSelectedBubble(clickedBubble);
+
+                }
+            } else {
+                // Otherwise, we either tapped the stack (which means we're collapsed
+                // and should expand) or the currently selected bubble (we're expanded
+                // and should collapse).
+                if (!maybeShowStackUserEducation()) {
+                    mBubbleData.setExpanded(!mBubbleData.isExpanded());
+                }
+            }
+        }
+    };
+
+    /**
+     * Touch listener set on each bubble view. This enables dragging and dismissing the stack (when
+     * collapsed), or individual bubbles (when expanded).
+     */
+    private RelativeTouchListener mBubbleTouchListener = new RelativeTouchListener() {
+
+        @Override
+        public boolean onDown(@NonNull View v, @NonNull MotionEvent ev) {
+            // If we're expanding or collapsing, consume but ignore all touch events.
+            if (mIsExpansionAnimating) {
+                return true;
+            }
+
+            if (mBubbleData.isExpanded()) {
+                maybeShowManageEducation(false /* show */);
+
+                // If we're expanded, tell the animation controller to prepare to drag this bubble,
+                // dispatching to the individual bubble magnet listener.
+                mExpandedAnimationController.prepareForBubbleDrag(
+                        v /* bubble */,
+                        mMagneticTarget,
+                        mIndividualBubbleMagnetListener);
+
+                // Save the magnetized individual bubble so we can dispatch touch events to it.
+                mMagnetizedObject = mExpandedAnimationController.getMagnetizedBubbleDraggingOut();
+            } else {
+                // If we're collapsed, prepare to drag the stack. Cancel active animations, set the
+                // animation controller, and hide the flyout.
+                mStackAnimationController.cancelStackPositionAnimations();
+                mBubbleContainer.setActiveController(mStackAnimationController);
+                hideFlyoutImmediate();
+
+                // Also, save the magnetized stack so we can dispatch touch events to it.
+                mMagnetizedObject = mStackAnimationController.getMagnetizedStack(mMagneticTarget);
+                mMagnetizedObject.setMagnetListener(mStackMagnetListener);
+            }
+
+            passEventToMagnetizedObject(ev);
+
+            // Bubbles are always interested in all touch events!
+            return true;
+        }
+
+        @Override
+        public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
+                float viewInitialY, float dx, float dy) {
+            // If we're expanding or collapsing, ignore all touch events.
+            if (mIsExpansionAnimating) {
+                return;
+            }
+
+            // Show the dismiss target, if we haven't already.
+            springInDismissTargetMaybe();
+
+            // First, see if the magnetized object consumes the event - if so, we shouldn't move the
+            // bubble since it's stuck to the target.
+            if (!passEventToMagnetizedObject(ev)) {
+                if (mBubbleData.isExpanded()) {
+                    mExpandedAnimationController.dragBubbleOut(
+                            v, viewInitialX + dx, viewInitialY + dy);
+                } else {
+                    hideStackUserEducation(false /* fromExpansion */);
+                    mStackAnimationController.moveStackFromTouch(
+                            viewInitialX + dx, viewInitialY + dy);
+                }
+            }
+        }
+
+        @Override
+        public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
+                float viewInitialY, float dx, float dy, float velX, float velY) {
+            // If we're expanding or collapsing, ignore all touch events.
+            if (mIsExpansionAnimating) {
+                return;
+            }
+
+            // First, see if the magnetized object consumes the event - if so, the bubble was
+            // released in the target or flung out of it, and we should ignore the event.
+            if (!passEventToMagnetizedObject(ev)) {
+                if (mBubbleData.isExpanded()) {
+                    mExpandedAnimationController.snapBubbleBack(v, velX, velY);
+                } else {
+                    // Fling the stack to the edge, and save whether or not it's going to end up on
+                    // the left side of the screen.
+                    mStackOnLeftOrWillBe =
+                            mStackAnimationController.flingStackThenSpringToEdge(
+                                    viewInitialX + dx, velX, velY) <= 0;
+
+                    updateBubbleZOrdersAndDotPosition(true /* animate */);
+
+                    logBubbleEvent(null /* no bubble associated with bubble stack move */,
+                            SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
+                }
+
+                hideDismissTarget();
+            }
+        }
+    };
+
+    /** Click listener set on the flyout, which expands the stack when the flyout is tapped. */
+    private OnClickListener mFlyoutClickListener = new OnClickListener() {
+        @Override
+        public void onClick(View view) {
+            if (maybeShowStackUserEducation()) {
+                // If we're showing user education, don't open the bubble show the education first
+                mBubbleToExpandAfterFlyoutCollapse = null;
+            } else {
+                mBubbleToExpandAfterFlyoutCollapse = mBubbleData.getSelectedBubble();
+            }
+
+            mFlyout.removeCallbacks(mHideFlyout);
+            mHideFlyout.run();
+        }
+    };
+
+    /** Touch listener for the flyout. This enables the drag-to-dismiss gesture on the flyout. */
+    private RelativeTouchListener mFlyoutTouchListener = new RelativeTouchListener() {
+
+        @Override
+        public boolean onDown(@NonNull View v, @NonNull MotionEvent ev) {
+            mFlyout.removeCallbacks(mHideFlyout);
+            return true;
+        }
+
+        @Override
+        public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
+                float viewInitialY, float dx, float dy) {
+            setFlyoutStateForDragLength(dx);
+        }
+
+        @Override
+        public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
+                float viewInitialY, float dx, float dy, float velX, float velY) {
+            final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
+            final boolean metRequiredVelocity =
+                    onLeft ? velX < -FLYOUT_DISMISS_VELOCITY : velX > FLYOUT_DISMISS_VELOCITY;
+            final boolean metRequiredDeltaX =
+                    onLeft
+                            ? dx < -mFlyout.getWidth() * FLYOUT_DRAG_PERCENT_DISMISS
+                            : dx > mFlyout.getWidth() * FLYOUT_DRAG_PERCENT_DISMISS;
+            final boolean isCancelFling = onLeft ? velX > 0 : velX < 0;
+            final boolean shouldDismiss = metRequiredVelocity
+                    || (metRequiredDeltaX && !isCancelFling);
+
+            mFlyout.removeCallbacks(mHideFlyout);
+            animateFlyoutCollapsed(shouldDismiss, velX);
+
+            maybeShowStackUserEducation();
+        }
+    };
+
     private ViewGroup mDismissTargetContainer;
     private PhysicsAnimator<View> mDismissTargetAnimator;
     private PhysicsAnimator.SpringConfig mDismissTargetSpring = new PhysicsAnimator.SpringConfig(
@@ -436,6 +622,7 @@
     private BubbleManageEducationView mManageEducationView;
     private boolean mAnimatingManageEducationAway;
 
+    @SuppressLint("ClickableViewAccessibility")
     public BubbleStackView(Context context, BubbleData data,
             @Nullable SurfaceSynchronizer synchronizer,
             FloatingContentCoordinator floatingContentCoordinator,
@@ -444,8 +631,6 @@
 
         mBubbleData = data;
         mInflater = LayoutInflater.from(context);
-        mTouchHandler = new BubbleTouchHandler(this, data, context);
-        setOnTouchListener(mTouchHandler);
 
         mSysUiState = sysUiState;
 
@@ -641,6 +826,18 @@
             mDesaturateAndDarkenPaint.setColorFilter(new ColorMatrixColorFilter(animatedMatrix));
             mDesaturateAndDarkenTargetView.setLayerPaint(mDesaturateAndDarkenPaint);
         });
+
+        // If the stack itself is touched, it means none of its touchable views (bubbles, flyouts,
+        // ActivityViews, etc.) were touched. Collapse the stack if it's expanded.
+        setOnTouchListener((view, ev) -> {
+            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+                if (mBubbleData.isExpanded()) {
+                    mBubbleData.setExpanded(false);
+                }
+            }
+
+            return false;
+        });
     }
 
     private void setUpUserEducation() {
@@ -690,6 +887,7 @@
         }
     }
 
+    @SuppressLint("ClickableViewAccessibility")
     private void setUpFlyout() {
         if (mFlyout != null) {
             removeView(mFlyout);
@@ -699,6 +897,8 @@
         mFlyout.animate()
                 .setDuration(FLYOUT_ALPHA_ANIMATION_DURATION)
                 .setInterpolator(new AccelerateDecelerateInterpolator());
+        mFlyout.setOnClickListener(mFlyoutClickListener);
+        mFlyout.setOnTouchListener(mFlyoutTouchListener);
         addView(mFlyout, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
     }
 
@@ -718,6 +918,7 @@
         mBubbleContainer.addView(mBubbleOverflow.getBtn(), overflowBtnIndex,
                 new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
 
+        mBubbleOverflow.getBtn().setOnClickListener(v -> setSelectedBubble(mBubbleOverflow));
     }
     /**
      * Handle theme changes.
@@ -920,6 +1121,7 @@
     }
 
     // via BubbleData.Listener
+    @SuppressLint("ClickableViewAccessibility")
     void addBubble(Bubble bubble) {
         if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "addBubble: " + bubble);
@@ -944,6 +1146,9 @@
         bubble.getIconView().setDotPositionOnLeft(
                 !mStackOnLeftOrWillBe /* onLeft */, false /* animate */);
 
+        bubble.getIconView().setOnClickListener(mBubbleClickListener);
+        bubble.getIconView().setOnTouchListener(mBubbleTouchListener);
+
         mBubbleContainer.addView(bubble.getIconView(), 0,
                 new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
         ViewClippingUtil.setClippingDeactivated(bubble.getIconView(), true, mClippingParameters);
@@ -1009,10 +1214,6 @@
         updatePointerPosition();
     }
 
-    void showOverflow() {
-        setSelectedBubble(mBubbleOverflow);
-    }
-
     /**
      * Changes the currently selected bubble. If the stack is already expanded, the newly selected
      * bubble will be shown immediately. This does not change the expanded state or change the
@@ -1177,14 +1378,6 @@
         }
     }
 
-    /*
-     * Sets the action to run to dismiss the currently dragging object (either the stack or an
-     * individual bubble).
-     */
-    public void setReleasedInDismissTargetAction(Runnable action) {
-        mReleasedInDismissTargetAction = action;
-    }
-
     /**
      * Dismiss the stack of bubbles.
      *
@@ -1201,54 +1394,6 @@
     }
 
     /**
-     * @return the view the touch event is on
-     */
-    @Nullable
-    public View getTargetView(MotionEvent event) {
-        float x = event.getRawX();
-        float y = event.getRawY();
-        if (mIsExpanded) {
-            if (isIntersecting(mBubbleContainer, x, y)) {
-                if (BubbleExperimentConfig.allowBubbleOverflow(mContext)
-                        && isIntersecting(mBubbleOverflow.getBtn(), x, y)) {
-                    return mBubbleOverflow.getBtn();
-                }
-                // Could be tapping or dragging a bubble while expanded
-                for (int i = 0; i < getBubbleCount(); i++) {
-                    BadgedImageView view = (BadgedImageView) mBubbleContainer.getChildAt(i);
-                    if (isIntersecting(view, x, y)) {
-                        return view;
-                    }
-                }
-            }
-            BubbleExpandedView bev = (BubbleExpandedView) mExpandedViewContainer.getChildAt(0);
-            if (bev.intersectingTouchableContent((int) x, (int) y)) {
-                return bev;
-            }
-            // Outside of the parts we care about.
-            return null;
-        } else if (mFlyout.getVisibility() == VISIBLE && isIntersecting(mFlyout, x, y)) {
-            return mFlyout;
-        } else if (mUserEducationView != null && mUserEducationView.getVisibility() == VISIBLE) {
-            View bubbleChild = mBubbleContainer.getChildAt(0);
-            if (isIntersecting(bubbleChild, x, y)) {
-                return this;
-            } else if (isIntersecting(mUserEducationView, x, y)) {
-                return mUserEducationView;
-            } else {
-                return null;
-            }
-        }
-
-        // If it wasn't an individual bubble in the expanded state, or the flyout, it's the stack.
-        return this;
-    }
-
-    View getFlyoutView() {
-        return mFlyout;
-    }
-
-    /**
      * @deprecated use {@link #setExpanded(boolean)} and
      * {@link BubbleData#setSelectedBubble(Bubble)}
      */
@@ -1385,124 +1530,70 @@
         }
     }
 
-    /** Called when the collapsed stack is tapped on. */
-    void onStackTapped() {
-        if (!maybeShowStackUserEducation()) {
-            mBubbleData.setExpanded(true);
-        }
+    /**
+     * This method is called by {@link android.app.ActivityView} because the BubbleStackView has a
+     * higher Z-index than the ActivityView (so that dragged-out bubbles are visible over the AV).
+     * ActivityView is asking BubbleStackView to subtract the stack's bounds from the provided
+     * touchable region, so that the ActivityView doesn't consume events meant for the stack. Due to
+     * the special nature of ActivityView, it does not respect the standard
+     * {@link #dispatchTouchEvent} and {@link #onInterceptTouchEvent} methods typically used for
+     * this purpose.
+     *
+     * BubbleStackView is MATCH_PARENT, so that bubbles can be positioned via their translation
+     * properties for performance reasons. This means that the default implementation of this method
+     * subtracts the entirety of the screen from the ActivityView's touchable region, resulting in
+     * it not receiving any touch events. This was previously addressed by returning false in the
+     * stack's {@link View#canReceivePointerEvents()} method, but this precluded the use of any
+     * touch handlers in the stack or its child views.
+     *
+     * To support touch handlers, we're overriding this method to leave the ActivityView's touchable
+     * region alone. The only touchable part of the stack that can ever overlap the AV is a
+     * dragged-out bubble that is animating back into the row of bubbles. It's not worth continually
+     * updating the touchable region to allow users to grab a bubble while it completes its ~50ms
+     * animation back to the bubble row.
+     *
+     * NOTE: Any future additions to the stack that obscure the ActivityView region will need their
+     * bounds subtracted here in order to receive touch events.
+     */
+    @Override
+    public void subtractObscuredTouchableRegion(Region touchableRegion, View view) {
+
     }
 
-    /** Called when a drag operation on an individual bubble has started. */
-    public void onBubbleDragStart(View bubble) {
-        if (DEBUG_BUBBLE_STACK_VIEW) {
-            Log.d(TAG, "onBubbleDragStart: bubble=" + ((BadgedImageView) bubble).getKey());
-        }
-
-        if (mBubbleOverflow != null && bubble.equals(mBubbleOverflow.getIconView())) {
-            return;
-        }
-
-        mExpandedAnimationController.prepareForBubbleDrag(bubble, mMagneticTarget);
-
-        // We're dragging an individual bubble, so set the magnetized object to the magnetized
-        // bubble.
-        mMagnetizedObject = mExpandedAnimationController.getMagnetizedBubbleDraggingOut();
-        mMagnetizedObject.setMagnetListener(mIndividualBubbleMagnetListener);
-
-        maybeShowManageEducation(false);
+    /**
+     * If you're here because you're not receiving touch events on a view that is a descendant of
+     * BubbleStackView, and you think BSV is intercepting them - it's not! You need to subtract the
+     * bounds of the view in question in {@link #subtractObscuredTouchableRegion}. The ActivityView
+     * consumes all touch events within its bounds, even for views like the BubbleStackView that are
+     * above it. It ignores typical view touch handling methods like this one and
+     * dispatchTouchEvent.
+     */
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        return super.onInterceptTouchEvent(ev);
     }
 
-    /** Called with the coordinates to which an individual bubble has been dragged. */
-    public void onBubbleDragged(View bubble, float x, float y) {
-        if (!mIsExpanded || mIsExpansionAnimating
-                || (mBubbleOverflow != null && bubble.equals(mBubbleOverflow.getIconView()))) {
-            return;
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        boolean dispatched = super.dispatchTouchEvent(ev);
+
+        // If a new bubble arrives while the collapsed stack is being dragged, it will be positioned
+        // at the front of the stack (under the touch position). Subsequent ACTION_MOVE events will
+        // then be passed to the new bubble, which will not consume them since it hasn't received an
+        // ACTION_DOWN yet. Work around this by passing MotionEvents directly to the touch handler
+        // until the current gesture ends with an ACTION_UP event.
+        if (!dispatched && !mIsExpanded && mIsGestureInProgress) {
+            dispatched = mBubbleTouchListener.onTouch(this /* view */, ev);
         }
 
-        mExpandedAnimationController.dragBubbleOut(bubble, x, y);
-        springInDismissTarget();
+        mIsGestureInProgress =
+                ev.getAction() != MotionEvent.ACTION_UP
+                        && ev.getAction() != MotionEvent.ACTION_CANCEL;
+
+        return dispatched;
     }
 
-    /** Called when a drag operation on an individual bubble has finished. */
-    public void onBubbleDragFinish(
-            View bubble, float x, float y, float velX, float velY) {
-        if (DEBUG_BUBBLE_STACK_VIEW) {
-            Log.d(TAG, "onBubbleDragFinish: bubble=" + bubble);
-        }
-
-        if (!mIsExpanded || mIsExpansionAnimating
-                || (mBubbleOverflow != null && bubble.equals(mBubbleOverflow.getIconView()))) {
-            return;
-        }
-
-        mExpandedAnimationController.snapBubbleBack(bubble, velX, velY);
-        hideDismissTarget();
-    }
-
-    /** Expands the clicked bubble. */
-    public void expandBubble(Bubble bubble) {
-        if (bubble != null && bubble.equals(mBubbleData.getSelectedBubble())) {
-            // If the bubble we're supposed to expand is the selected bubble, that means the
-            // overflow bubble is currently expanded. Don't tell BubbleData to set this bubble as
-            // selected, since it already is. Just call the stack's setSelectedBubble to expand it.
-            setSelectedBubble(bubble);
-        } else {
-            mBubbleData.setSelectedBubble(bubble);
-        }
-    }
-
-    void onDragStart() {
-        if (DEBUG_BUBBLE_STACK_VIEW) {
-            Log.d(TAG, "onDragStart()");
-        }
-        if (mIsExpanded || mIsExpansionAnimating) {
-            if (DEBUG_BUBBLE_STACK_VIEW) {
-                Log.d(TAG, "mIsExpanded or mIsExpansionAnimating");
-            }
-            return;
-        }
-        mStackAnimationController.cancelStackPositionAnimations();
-        mBubbleContainer.setActiveController(mStackAnimationController);
-        hideFlyoutImmediate();
-
-        // Since we're dragging the stack, set the magnetized object to the magnetized stack.
-        mMagnetizedObject = mStackAnimationController.getMagnetizedStack(mMagneticTarget);
-        mMagnetizedObject.setMagnetListener(mStackMagnetListener);
-    }
-
-    void onDragged(float x, float y) {
-        if (mIsExpanded || mIsExpansionAnimating) {
-            return;
-        }
-
-        hideStackUserEducation(false /* fromExpansion */);
-        springInDismissTarget();
-        mStackAnimationController.moveStackFromTouch(x, y);
-    }
-
-    void onDragFinish(float x, float y, float velX, float velY) {
-        if (DEBUG_BUBBLE_STACK_VIEW) {
-            Log.d(TAG, "onDragFinish");
-        }
-
-        if (mIsExpanded || mIsExpansionAnimating) {
-            return;
-        }
-
-        final float newStackX = mStackAnimationController.flingStackThenSpringToEdge(x, velX, velY);
-        logBubbleEvent(null /* no bubble associated with bubble stack move */,
-                SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
-
-        mStackOnLeftOrWillBe = newStackX <= 0;
-        updateBubbleZOrdersAndDotPosition(true /* animate */);
-        hideDismissTarget();
-    }
-
-    void onFlyoutDragStart() {
-        mFlyout.removeCallbacks(mHideFlyout);
-    }
-
-    void onFlyoutDragged(float deltaX) {
+    void setFlyoutStateForDragLength(float deltaX) {
         // This shouldn't happen, but if it does, just wait until the flyout lays out. This method
         // is continually called.
         if (mFlyout.getWidth() <= 0) {
@@ -1538,61 +1629,29 @@
         mFlyout.setTranslationX(mFlyout.getRestingTranslationX() + overscrollTranslation);
     }
 
-    void onFlyoutTapped() {
-        if (maybeShowStackUserEducation()) {
-            // If we're showing user education, don't open the bubble show the education first
-            mBubbleToExpandAfterFlyoutCollapse = null;
-        } else {
-            mBubbleToExpandAfterFlyoutCollapse = mBubbleData.getSelectedBubble();
-        }
-
-        mFlyout.removeCallbacks(mHideFlyout);
-        mHideFlyout.run();
-    }
-
-    /**
-     * Called when the flyout drag has finished, and returns true if the gesture successfully
-     * dismissed the flyout.
-     */
-    void onFlyoutDragFinished(float deltaX, float velX) {
-        final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
-        final boolean metRequiredVelocity =
-                onLeft ? velX < -FLYOUT_DISMISS_VELOCITY : velX > FLYOUT_DISMISS_VELOCITY;
-        final boolean metRequiredDeltaX =
-                onLeft
-                        ? deltaX < -mFlyout.getWidth() * FLYOUT_DRAG_PERCENT_DISMISS
-                        : deltaX > mFlyout.getWidth() * FLYOUT_DRAG_PERCENT_DISMISS;
-        final boolean isCancelFling = onLeft ? velX > 0 : velX < 0;
-        final boolean shouldDismiss = metRequiredVelocity || (metRequiredDeltaX && !isCancelFling);
-
-        mFlyout.removeCallbacks(mHideFlyout);
-        animateFlyoutCollapsed(shouldDismiss, velX);
-
-        maybeShowStackUserEducation();
-    }
-
-    /**
-     * Called when the first touch event of a gesture (stack drag, bubble drag, flyout drag, etc.)
-     * is received.
-     */
-    void onGestureStart() {
-        mIsGestureInProgress = true;
-    }
-
-    /** Called when a gesture is completed or cancelled. */
-    void onGestureFinished() {
-        mIsGestureInProgress = false;
-
-        if (mIsExpanded) {
-            mExpandedAnimationController.onGestureFinished();
-        }
-    }
-
     /** Passes the MotionEvent to the magnetized object and returns true if it was consumed. */
-    boolean passEventToMagnetizedObject(MotionEvent event) {
+    private boolean passEventToMagnetizedObject(MotionEvent event) {
         return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event);
     }
 
+    /**
+     * Dismisses the magnetized object - either an individual bubble, if we're expanded, or the
+     * stack, if we're collapsed.
+     */
+    private void dismissMagnetizedObject() {
+        if (mIsExpanded) {
+            final View draggedOutBubbleView = (View) mMagnetizedObject.getUnderlyingObject();
+            final Bubble draggedOutBubble = mBubbleData.getBubbleWithView(draggedOutBubbleView);
+
+            if (mBubbleData.hasBubbleWithKey(draggedOutBubble.getKey())) {
+                mBubbleData.notificationEntryRemoved(
+                        draggedOutBubble.getEntry(), BubbleController.DISMISS_USER_GESTURE);
+            }
+        } else {
+            mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
+        }
+    }
+
     /** Prepares and starts the desaturate/darken animation on the bubble stack. */
     private void animateDesaturateAndDarken(View targetView, boolean desaturateAndDarken) {
         mDesaturateAndDarkenTargetView = targetView;
@@ -1624,7 +1683,7 @@
     }
 
     /** Animates in the dismiss target. */
-    private void springInDismissTarget() {
+    private void springInDismissTargetMaybe() {
         if (mShowingDismiss) {
             return;
         }
@@ -1827,13 +1886,6 @@
         return 0;
     }
 
-    private boolean isIntersecting(View view, float x, float y) {
-        mTempLoc = view.getLocationOnScreen();
-        mTempRect.set(mTempLoc[0], mTempLoc[1], mTempLoc[0] + view.getWidth(),
-                mTempLoc[1] + view.getHeight());
-        return mTempRect.contains(x, y);
-    }
-
     private void requestUpdate() {
         if (mViewUpdatedRequested || mIsExpansionAnimating) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
deleted file mode 100644
index 132c45f..0000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright (C) 2012 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 android.content.Context;
-import android.graphics.PointF;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-
-import com.android.systemui.Dependency;
-
-/**
- * Handles interpreting touches on a {@link BubbleStackView}. This includes expanding, collapsing,
- * dismissing, and flings.
- */
-class BubbleTouchHandler implements View.OnTouchListener {
-
-    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 boolean mMovedEnough;
-    private int mTouchSlopSquared;
-    private VelocityTracker mVelocityTracker;
-
-    /** View that was initially touched, when we received the first ACTION_DOWN event. */
-    private View mTouchedView;
-
-    BubbleTouchHandler(BubbleStackView stackView,
-            BubbleData bubbleData, Context context) {
-        final int touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
-        mTouchSlopSquared = touchSlop * touchSlop;
-        mBubbleData = bubbleData;
-        mStack = stackView;
-    }
-
-    @Override
-    public boolean onTouch(View v, MotionEvent event) {
-        final int action = event.getActionMasked();
-
-        // If we aren't currently in the process of touching a view, figure out what we're touching.
-        // It'll be the stack, an individual bubble, or nothing.
-        if (mTouchedView == null) {
-            mTouchedView = mStack.getTargetView(event);
-        }
-
-        // 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) {
-            mBubbleData.setExpanded(false);
-            mStack.hideStackUserEducation(false /* fromExpansion */);
-            resetForNextGesture();
-            return false;
-        }
-
-        if (!(mTouchedView instanceof BadgedImageView)
-                && !(mTouchedView instanceof BubbleStackView)
-                && !(mTouchedView instanceof BubbleFlyoutView)) {
-
-            // Not touching anything touchable, but we shouldn't collapse (e.g. touching edge
-            // of expanded view).
-            mStack.maybeShowManageEducation(false);
-            resetForNextGesture();
-            return false;
-        }
-
-        final boolean isStack = mStack.equals(mTouchedView);
-        final boolean isFlyout = mStack.getFlyoutView().equals(mTouchedView);
-        final float rawX = event.getRawX();
-        final float rawY = event.getRawY();
-
-        // The coordinates of the touch event, in terms of the touched view's position.
-        final float viewX = mViewPositionOnTouchDown.x + rawX - mTouchDown.x;
-        final float viewY = mViewPositionOnTouchDown.y + rawY - mTouchDown.y;
-        switch (action) {
-            case MotionEvent.ACTION_DOWN:
-                trackMovement(event);
-
-                mTouchDown.set(rawX, rawY);
-                mStack.onGestureStart();
-
-                if (isStack) {
-                    mViewPositionOnTouchDown.set(mStack.getStackPosition());
-
-                    // Dismiss the entire stack if it's released in the dismiss target.
-                    mStack.setReleasedInDismissTargetAction(
-                            () -> mController.dismissStack(BubbleController.DISMISS_USER_GESTURE));
-                    mStack.onDragStart();
-                    mStack.passEventToMagnetizedObject(event);
-                } else if (isFlyout) {
-                    mStack.onFlyoutDragStart();
-                } else {
-                    mViewPositionOnTouchDown.set(
-                            mTouchedView.getTranslationX(), mTouchedView.getTranslationY());
-
-                    // Dismiss only the dragged-out bubble if it's released in the target.
-                    final String individualBubbleKey = ((BadgedImageView) mTouchedView).getKey();
-                    mStack.setReleasedInDismissTargetAction(() -> {
-                        final Bubble bubble =
-                                mBubbleData.getBubbleWithKey(individualBubbleKey);
-                        // bubble can be null if the user is in the middle of
-                        // dismissing the bubble, but the app also sent a cancel
-                        if (bubble != null) {
-                            mController.removeBubble(bubble.getEntry(),
-                                    BubbleController.DISMISS_USER_GESTURE);
-                        }
-                    });
-
-                    mStack.onBubbleDragStart(mTouchedView);
-                    mStack.passEventToMagnetizedObject(event);
-                }
-
-                break;
-            case MotionEvent.ACTION_MOVE:
-                trackMovement(event);
-                final float deltaX = rawX - mTouchDown.x;
-                final float deltaY = rawY - mTouchDown.y;
-
-                if ((deltaX * deltaX) + (deltaY * deltaY) > mTouchSlopSquared && !mMovedEnough) {
-                    mMovedEnough = true;
-                }
-
-                if (mMovedEnough) {
-                    if (isFlyout) {
-                        mStack.onFlyoutDragged(deltaX);
-                    } else if (!mStack.passEventToMagnetizedObject(event)) {
-                        // If the magnetic target doesn't consume the event, drag the stack or
-                        // bubble.
-                        if (isStack) {
-                            mStack.onDragged(viewX, viewY);
-                        } else {
-                            mStack.onBubbleDragged(mTouchedView, viewX, viewY);
-                        }
-                    }
-                }
-                break;
-
-            case MotionEvent.ACTION_CANCEL:
-                resetForNextGesture();
-                break;
-
-            case MotionEvent.ACTION_UP:
-                trackMovement(event);
-                mVelocityTracker.computeCurrentVelocity(/* maxVelocity */ 1000);
-                final float velX = mVelocityTracker.getXVelocity();
-                final float velY = mVelocityTracker.getYVelocity();
-
-                if (isFlyout && mMovedEnough) {
-                    mStack.onFlyoutDragFinished(rawX - mTouchDown.x /* deltaX */, velX);
-                } else if (isFlyout) {
-                    if (!mBubbleData.isExpanded() && !mMovedEnough) {
-                        mStack.onFlyoutTapped();
-                    }
-                } else if (mMovedEnough) {
-                    if (!mStack.passEventToMagnetizedObject(event)) {
-                        // If the magnetic target didn't consume the event, tell the stack to finish
-                        // the drag.
-                        if (isStack) {
-                            mStack.onDragFinish(viewX, viewY, velX, velY);
-                        } else {
-                            mStack.onBubbleDragFinish(mTouchedView, viewX, viewY, velX, velY);
-                        }
-                    }
-                } else if (mTouchedView == mStack.getExpandedBubbleView()) {
-                    mBubbleData.setExpanded(false);
-                } else if (isStack) {
-                    mStack.onStackTapped();
-                } else {
-                    final String key = ((BadgedImageView) mTouchedView).getKey();
-                    if (key == BubbleOverflow.KEY) {
-                        mStack.showOverflow();
-                    } else {
-                        mStack.expandBubble(mBubbleData.getBubbleWithKey(key));
-                    }
-                }
-                resetForNextGesture();
-                break;
-        }
-
-        return true;
-    }
-
-    /** Clears all touch-related state. */
-    private void resetForNextGesture() {
-        if (mVelocityTracker != null) {
-            mVelocityTracker.recycle();
-            mVelocityTracker = null;
-        }
-
-        mTouchedView = null;
-        mMovedEnough = false;
-
-        mStack.onGestureFinished();
-    }
-
-    private void trackMovement(MotionEvent event) {
-        if (mVelocityTracker == null) {
-            mVelocityTracker = VelocityTracker.obtain();
-        }
-        mVelocityTracker.addMovement(event);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index a0b4938..d974adc 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -252,8 +252,14 @@
         mSpringToTouchOnNextMotionEvent = true;
     }
 
-    /** Prepares the given bubble to be dragged out. */
-    public void prepareForBubbleDrag(View bubble, MagnetizedObject.MagneticTarget target) {
+    /**
+     * Prepares the given bubble view to be dragged out, using the provided magnetic target and
+     * listener.
+     */
+    public void prepareForBubbleDrag(
+            View bubble,
+            MagnetizedObject.MagneticTarget target,
+            MagnetizedObject.MagnetListener listener) {
         mLayout.cancelAnimationsOnView(bubble);
 
         bubble.setTranslationZ(Short.MAX_VALUE);
@@ -277,6 +283,7 @@
             }
         };
         mMagnetizedBubbleDraggingOut.addTarget(target);
+        mMagnetizedBubbleDraggingOut.setMagnetListener(listener);
         mMagnetizedBubbleDraggingOut.setHapticsEnabled(true);
         mMagnetizedBubbleDraggingOut.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
index c292769..b1bbafc 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
@@ -1117,9 +1117,4 @@
             mAssociatedController = controller;
         }
     }
-
-    @Override
-    protected boolean canReceivePointerEvents() {
-        return false;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
index d33cd94..8fd840e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
@@ -56,13 +56,12 @@
             enabled: Boolean,
             offset: Int = 0
         ): RenderInfo {
-            val (fg, bg) = deviceColorMap.getValue(deviceType)
-
-            val iconKey = if (offset > 0) {
+            val key = if (offset > 0) {
                 deviceType * BUCKET_SIZE + offset
             } else deviceType
 
-            val iconState = deviceIconMap.getValue(iconKey)
+            val (fg, bg) = deviceColorMap.getValue(key)
+            val iconState = deviceIconMap.getValue(key)
             val resourceId = iconState[enabled]
             var icon: Drawable?
             if (resourceId == APP_ICON_ID) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
index aa5ebaa..b7658a9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
@@ -46,19 +46,6 @@
     /** Updates seek bar views when the data model changes. */
     @UiThread
     override fun onChanged(data: SeekBarViewModel.Progress) {
-        if (data.enabled && seekBarView.visibility == View.GONE) {
-            seekBarView.visibility = View.VISIBLE
-            elapsedTimeView.visibility = View.VISIBLE
-            totalTimeView.visibility = View.VISIBLE
-        } else if (!data.enabled && seekBarView.visibility == View.VISIBLE) {
-            seekBarView.visibility = View.GONE
-            elapsedTimeView.visibility = View.GONE
-            totalTimeView.visibility = View.GONE
-            return
-        }
-
-        // TODO: update the style of the disabled progress bar
-        seekBarView.setEnabled(data.seekAvailable)
 
         data.color?.let {
             var tintList = ColorStateList.valueOf(it)
@@ -71,6 +58,17 @@
             totalTimeView.setTextColor(it)
         }
 
+        if (!data.enabled) {
+            seekBarView.setEnabled(false)
+            seekBarView.getThumb().setAlpha(0)
+            elapsedTimeView.setText("")
+            totalTimeView.setText("")
+            return
+        }
+
+        seekBarView.getThumb().setAlpha(if (data.seekAvailable) 255 else 0)
+        seekBarView.setEnabled(data.seekAvailable)
+
         data.elapsedTime?.let {
             seekBarView.setProgress(it)
             elapsedTimeView.setText(DateUtils.formatElapsedTime(
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index b10dd93..d2994ae 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -103,6 +103,7 @@
         @Override
         public void onPipAnimationEnd(SurfaceControl.Transaction tx,
                 PipAnimationController.PipTransitionAnimator animator) {
+            finishResize(tx, animator.getDestinationBounds(), animator.getTransitionDirection());
             mMainHandler.post(() -> {
                 for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
                     final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
@@ -110,7 +111,6 @@
                             animator.getTransitionDirection());
                 }
             });
-            finishResize(tx, animator.getDestinationBounds(), animator.getTransitionDirection());
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index a192afc..a8a5d89 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -16,12 +16,10 @@
 
 package com.android.systemui.pip.phone;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityManager.StackInfo;
 import android.app.IActivityTaskManager;
 import android.content.Context;
 import android.graphics.Rect;
@@ -169,15 +167,7 @@
      */
     void synchronizePinnedStackBounds() {
         cancelAnimations();
-        try {
-            StackInfo stackInfo = mActivityTaskManager.getStackInfo(
-                    WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
-            if (stackInfo != null) {
-                mBounds.set(stackInfo.bounds);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to get pinned stack bounds");
-        }
+        mBounds.set(mPipTaskOrganizer.getLastReportedBounds());
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 55d3e83..729f934 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController
 import com.android.systemui.statusbar.phone.PanelExpansionListener
+import com.android.systemui.statusbar.phone.ScrimController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import java.io.FileDescriptor
 import java.io.PrintWriter
@@ -107,6 +108,16 @@
         }
 
     /**
+     * Force stop blur effect when necessary.
+     */
+    private var scrimsVisible: Boolean = false
+        set(value) {
+            if (field == value) return
+            field = value
+            scheduleUpdate()
+        }
+
+    /**
      * Blur radius of the wake-up animation on this frame.
      */
     private var wakeAndUnlockBlurRadius = 0
@@ -142,7 +153,13 @@
         if (showingHomeControls) {
             globalActionsRadius = 0
         }
-        val blur = max(shadeRadius.toInt(), globalActionsRadius)
+        var blur = max(shadeRadius.toInt(), globalActionsRadius)
+
+        // Make blur be 0 if it is necessary to stop blur effect.
+        if (scrimsVisible) {
+            blur = 0
+        }
+
         blurUtils.applyBlur(blurRoot?.viewRootImpl ?: root.viewRootImpl, blur)
         try {
             wallpaperManager.setWallpaperZoomOut(root.windowToken,
@@ -202,6 +219,10 @@
                 brightnessMirrorSpring.finishIfRunning()
             }
         }
+
+        override fun onDozeAmountChanged(linear: Float, eased: Float) {
+            wakeAndUnlockBlurRadius = blurUtils.blurRadiusOfRatio(eased)
+        }
     }
 
     init {
@@ -210,6 +231,10 @@
             keyguardStateController.addCallback(keyguardStateCallback)
         }
         statusBarStateController.addCallback(statusBarStateCallback)
+        notificationShadeWindowController.setScrimsVisibilityListener {
+            // Stop blur effect when scrims is opaque to avoid unnecessary GPU composition.
+            visibility -> scrimsVisible = visibility == ScrimController.OPAQUE
+        }
     }
 
     /**
@@ -225,7 +250,8 @@
 
     private fun updateShadeBlur() {
         var newBlur = 0
-        if (statusBarStateController.state == StatusBarState.SHADE) {
+        val state = statusBarStateController.state
+        if (state == StatusBarState.SHADE || state == StatusBarState.SHADE_LOCKED) {
             val animatedBlur =
                     Interpolators.SHADE_ANIMATION.getInterpolation(
                             MathUtils.constrain(shadeExpansion / 0.15f, 0f, 1f))
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
index e1e679f..462b42a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
@@ -61,6 +61,7 @@
 import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.function.Consumer;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -92,6 +93,7 @@
     private final State mCurrentState = new State();
     private OtherwisedCollapsedListener mListener;
     private ForcePluginOpenListener mForcePluginOpenListener;
+    private Consumer<Integer> mScrimsVisibilityListener;
     private final ArrayList<WeakReference<StatusBarWindowCallback>>
             mCallbacks = Lists.newArrayList();
 
@@ -150,6 +152,16 @@
         mCallbacks.add(new WeakReference<StatusBarWindowCallback>(callback));
     }
 
+    /**
+     * Register a listener to monitor scrims visibility
+     * @param listener A listener to monitor scrims visibility
+     */
+    public void setScrimsVisibilityListener(Consumer<Integer> listener) {
+        if (listener != null && mScrimsVisibilityListener != listener) {
+            mScrimsVisibilityListener = listener;
+        }
+    }
+
     private boolean shouldEnableKeyguardScreenRotation() {
         Resources res = mContext.getResources();
         return SystemProperties.getBoolean("lockscreen.rot_override", false)
@@ -477,6 +489,7 @@
     public void setScrimsVisibility(int scrimsVisibility) {
         mCurrentState.mScrimsVisibility = scrimsVisibility;
         apply(mCurrentState);
+        mScrimsVisibilityListener.accept(scrimsVisibility);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt b/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt
new file mode 100644
index 0000000..d65b285
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2020 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.util
+
+import android.graphics.PointF
+import android.os.Handler
+import android.view.MotionEvent
+import android.view.VelocityTracker
+import android.view.View
+import android.view.ViewConfiguration
+import kotlin.math.hypot
+
+/**
+ * Listener which receives [onDown], [onMove], and [onUp] events, with relevant information about
+ * the coordinates of the touch and the view relative to the initial ACTION_DOWN event and the
+ * view's initial position.
+ */
+abstract class RelativeTouchListener : View.OnTouchListener {
+
+    /**
+     * Called when an ACTION_DOWN event is received for the given view.
+     *
+     * @return False if the object is not interested in MotionEvents at this time, or true if we
+     * should consume this event and subsequent events, and begin calling [onMove].
+     */
+    abstract fun onDown(v: View, ev: MotionEvent): Boolean
+
+    /**
+     * Called when an ACTION_MOVE event is received for the given view. This signals that the view
+     * is being dragged.
+     *
+     * @param viewInitialX The view's translationX value when this touch gesture started.
+     * @param viewInitialY The view's translationY value when this touch gesture started.
+     * @param dx Horizontal distance covered since the initial ACTION_DOWN event, in pixels.
+     * @param dy Vertical distance covered since the initial ACTION_DOWN event, in pixels.
+     */
+    abstract fun onMove(
+        v: View,
+        ev: MotionEvent,
+        viewInitialX: Float,
+        viewInitialY: Float,
+        dx: Float,
+        dy: Float
+    )
+
+    /**
+     * Called when an ACTION_UP event is received for the given view. This signals that a drag or
+     * fling gesture has completed.
+     *
+     * @param viewInitialX The view's translationX value when this touch gesture started.
+     * @param viewInitialY The view's translationY value when this touch gesture started.
+     * @param dx Horizontal distance covered, in pixels.
+     * @param dy Vertical distance covered, in pixels.
+     * @param velX The final horizontal velocity of the gesture, in pixels/second.
+     * @param velY The final vertical velocity of the gesture, in pixels/second.
+     */
+    abstract fun onUp(
+        v: View,
+        ev: MotionEvent,
+        viewInitialX: Float,
+        viewInitialY: Float,
+        dx: Float,
+        dy: Float,
+        velX: Float,
+        velY: Float
+    )
+
+    /** The raw coordinates of the last ACTION_DOWN event. */
+    private val touchDown = PointF()
+
+    /** The coordinates of the view, at the time of the last ACTION_DOWN event. */
+    private val viewPositionOnTouchDown = PointF()
+
+    private val velocityTracker = VelocityTracker.obtain()
+
+    private var touchSlop: Int = -1
+    private var movedEnough = false
+
+    private val handler = Handler()
+    private var performedLongClick = false
+
+    @Suppress("UNCHECKED_CAST")
+    override fun onTouch(v: View, ev: MotionEvent): Boolean {
+        addMovement(ev)
+
+        val dx = ev.rawX - touchDown.x
+        val dy = ev.rawY - touchDown.y
+
+        when (ev.action) {
+            MotionEvent.ACTION_DOWN -> {
+                if (!onDown(v, ev)) {
+                    return false
+                }
+
+                // Grab the touch slop, it might have changed if the config changed since the
+                // last gesture.
+                touchSlop = ViewConfiguration.get(v.context).scaledTouchSlop
+
+                touchDown.set(ev.rawX, ev.rawY)
+                viewPositionOnTouchDown.set(v.translationX, v.translationY)
+
+                performedLongClick = false
+                handler.postDelayed({
+                    performedLongClick = v.performLongClick()
+                }, ViewConfiguration.getLongPressTimeout().toLong())
+            }
+
+            MotionEvent.ACTION_MOVE -> {
+                if (!movedEnough && hypot(dx, dy) > touchSlop && !performedLongClick) {
+                    movedEnough = true
+                    handler.removeCallbacksAndMessages(null)
+                }
+
+                if (movedEnough) {
+                    onMove(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy)
+                }
+            }
+
+            MotionEvent.ACTION_UP -> {
+                if (movedEnough) {
+                    velocityTracker.computeCurrentVelocity(1000 /* units */)
+                    onUp(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy,
+                            velocityTracker.xVelocity, velocityTracker.yVelocity)
+                } else if (!performedLongClick) {
+                    v.performClick()
+                } else {
+                    handler.removeCallbacksAndMessages(null)
+                }
+
+                velocityTracker.clear()
+                movedEnough = false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Adds a movement to the velocity tracker using raw screen coordinates.
+     */
+    private fun addMovement(event: MotionEvent) {
+        val deltaX = event.rawX - event.x
+        val deltaY = event.rawY - event.y
+        event.offsetLocation(deltaX, deltaY)
+        velocityTracker.addMovement(event)
+        event.offsetLocation(-deltaX, -deltaY)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
index 475023e..5227aaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
@@ -160,7 +160,7 @@
                 LOW_BMP_HEIGHT /* bmpHeight */,
                 LOW_BMP_WIDTH /* surfaceWidth */,
                 LOW_BMP_HEIGHT /* surfaceHeight */,
-                true /* assertion */);
+                false /* assertion */);
     }
 
     @Test
@@ -172,7 +172,7 @@
                 INVALID_BMP_HEIGHT /* bmpHeight */,
                 ImageWallpaper.GLEngine.MIN_SURFACE_WIDTH /* surfaceWidth */,
                 ImageWallpaper.GLEngine.MIN_SURFACE_HEIGHT /* surfaceHeight */,
-                true /* assertion */);
+                false /* assertion */);
     }
 
     private void verifySurfaceSizeAndAssertTransition(int bmpWidth, int bmpHeight,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 037f04ec..e472de3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -374,7 +374,7 @@
         assertNotNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().getKey()));
         assertTrue(mBubbleController.hasBubbles());
 
-        mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
+        mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
         assertFalse(mNotificationShadeWindowController.getBubblesShowing());
         verify(mNotificationEntryManager, times(3)).updateNotifications(any());
         assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
@@ -399,7 +399,7 @@
 
         // Expand the stack
         BubbleStackView stackView = mBubbleController.getStackView();
-        mBubbleController.expandStack();
+        mBubbleData.setExpanded(true);
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
         assertTrue(mNotificationShadeWindowController.getBubbleExpanded());
@@ -436,7 +436,7 @@
 
         // Expand
         BubbleStackView stackView = mBubbleController.getStackView();
-        mBubbleController.expandStack();
+        mBubbleData.setExpanded(true);
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
 
@@ -448,7 +448,7 @@
                 mRow2.getEntry()));
 
         // Switch which bubble is expanded
-        mBubbleController.selectBubble(mRow.getEntry().getKey());
+        mBubbleData.setSelectedBubble(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
         mBubbleData.setExpanded(true);
         assertEquals(mRow.getEntry(),
                 mBubbleData.getBubbleWithKey(stackView.getExpandedBubble().getKey()).getEntry());
@@ -482,7 +482,7 @@
         assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
 
         // Expand
-        mBubbleController.expandStack();
+        mBubbleData.setExpanded(true);
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
 
@@ -510,7 +510,7 @@
         assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
 
         // Expand
-        mBubbleController.expandStack();
+        mBubbleData.setExpanded(true);
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
 
@@ -544,7 +544,7 @@
 
         // Expand
         BubbleStackView stackView = mBubbleController.getStackView();
-        mBubbleController.expandStack();
+        mBubbleData.setExpanded(true);
 
         assertTrue(mSysUiStateBubblesExpanded);
 
@@ -726,7 +726,7 @@
     public void testDeleteIntent_dismissStack() throws PendingIntent.CanceledException {
         mBubbleController.updateBubble(mRow.getEntry());
         mBubbleController.updateBubble(mRow2.getEntry());
-        mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
+        mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
         verify(mDeleteIntent, times(2)).send();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
index 545de21..5f4f2ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -321,7 +321,7 @@
         assertNotNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().getKey()));
         assertTrue(mBubbleController.hasBubbles());
 
-        mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
+        mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
         assertFalse(mNotificationShadeWindowController.getBubblesShowing());
         verify(mNotifCallback, times(3)).invalidateNotifications(anyString());
         assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
@@ -344,7 +344,7 @@
 
         // Expand the stack
         BubbleStackView stackView = mBubbleController.getStackView();
-        mBubbleController.expandStack();
+        mBubbleData.setExpanded(true);
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
         assertTrue(mNotificationShadeWindowController.getBubbleExpanded());
@@ -376,7 +376,7 @@
 
         // Expand
         BubbleStackView stackView = mBubbleController.getStackView();
-        mBubbleController.expandStack();
+        mBubbleData.setExpanded(true);
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
 
@@ -385,7 +385,7 @@
         assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow2.getEntry()));
 
         // Switch which bubble is expanded
-        mBubbleController.selectBubble(mRow.getEntry().getKey());
+        mBubbleData.setSelectedBubble(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
         mBubbleData.setExpanded(true);
         assertEquals(mRow.getEntry(),
                 mBubbleData.getBubbleWithKey(stackView.getExpandedBubble().getKey()).getEntry());
@@ -416,7 +416,7 @@
         assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
 
         // Expand
-        mBubbleController.expandStack();
+        mBubbleData.setExpanded(true);
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
 
@@ -442,7 +442,7 @@
         assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
 
         // Expand
-        mBubbleController.expandStack();
+        mBubbleData.setExpanded(true);
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
 
@@ -474,7 +474,7 @@
 
         // Expand
         BubbleStackView stackView = mBubbleController.getStackView();
-        mBubbleController.expandStack();
+        mBubbleData.setExpanded(true);
 
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
@@ -628,7 +628,7 @@
     public void testDeleteIntent_dismissStack() throws PendingIntent.CanceledException {
         mBubbleController.updateBubble(mRow.getEntry());
         mBubbleController.updateBubble(mRow2.getEntry());
-        mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
+        mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
         verify(mDeleteIntent, times(2)).send();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
index 260f520..d407b8a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
@@ -67,10 +67,11 @@
         val isEnabled = false
         val data = SeekBarViewModel.Progress(isEnabled, false, null, null, null)
         observer.onChanged(data)
-        // THEN seek bar visibility is set to GONE
-        assertThat(seekBarView.getVisibility()).isEqualTo(View.GONE)
-        assertThat(elapsedTimeView.getVisibility()).isEqualTo(View.GONE)
-        assertThat(totalTimeView.getVisibility()).isEqualTo(View.GONE)
+        // THEN seek bar shows just a line with no text
+        assertThat(seekBarView.isEnabled()).isFalse()
+        assertThat(seekBarView.getThumb().getAlpha()).isEqualTo(0)
+        assertThat(elapsedTimeView.getText()).isEqualTo("")
+        assertThat(totalTimeView.getText()).isEqualTo("")
     }
 
     @Test
diff --git a/packages/Tethering/tests/integration/Android.bp b/packages/Tethering/tests/integration/Android.bp
index 1a1c30d..620261b 100644
--- a/packages/Tethering/tests/integration/Android.bp
+++ b/packages/Tethering/tests/integration/Android.bp
@@ -39,4 +39,9 @@
         "android.test.base",
         "android.test.mock",
     ],
+    jni_libs: [
+        // For mockito extended
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
 }
diff --git a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index dbd68ef..b02bb23 100644
--- a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -205,7 +205,7 @@
                 requestWithStaticIpv4(localAddr, clientAddr));
 
         mTetheringEventCallback.awaitInterfaceTethered();
-        assertInterfaceHasIpAddress(iface, clientAddr);
+        assertInterfaceHasIpAddress(iface, localAddr);
 
         byte[] client1 = MacAddress.fromString("1:2:3:4:5:6").toByteArray();
         byte[] client2 = MacAddress.fromString("a:b:c:d:e:f").toByteArray();
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index 93bffe9..52a82dd 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -36,10 +36,12 @@
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.Slog;
 import android.util.Xml;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.PackageMonitor;
 
 import libcore.io.IoUtils;
 
@@ -261,22 +263,7 @@
 
             // And reset to the wallpaper service we should be using
             ComponentName wpService = parseWallpaperComponent(infoStage, "wp");
-            if (servicePackageExists(wpService)) {
-                Slog.i(TAG, "Using wallpaper service " + wpService);
-                mWm.setWallpaperComponent(wpService, UserHandle.USER_SYSTEM);
-                if (!lockImageStage.exists()) {
-                    // We have a live wallpaper and no static lock image,
-                    // allow live wallpaper to show "through" on lock screen.
-                    mWm.clear(FLAG_LOCK);
-                }
-            } else {
-                // If we've restored a live wallpaper, but the component doesn't exist,
-                // we should log it as an error so we can easily identify the problem
-                // in reports from users
-                if (wpService != null) {
-                    Slog.e(TAG, "Wallpaper service " + wpService + " isn't available.");
-                }
-            }
+            updateWallpaperComponent(wpService, !lockImageStage.exists());
         } catch (Exception e) {
             Slog.e(TAG, "Unable to restore wallpaper: " + e.getMessage());
         } finally {
@@ -293,6 +280,28 @@
         }
     }
 
+    @VisibleForTesting
+    void updateWallpaperComponent(ComponentName wpService, boolean applyToLock) throws IOException {
+        if (servicePackageExists(wpService)) {
+            Slog.i(TAG, "Using wallpaper service " + wpService);
+            mWm.setWallpaperComponent(wpService, UserHandle.USER_SYSTEM);
+            if (applyToLock) {
+                // We have a live wallpaper and no static lock image,
+                // allow live wallpaper to show "through" on lock screen.
+                mWm.clear(FLAG_LOCK);
+            }
+        } else {
+            // If we've restored a live wallpaper, but the component doesn't exist,
+            // we should log it as an error so we can easily identify the problem
+            // in reports from users
+            if (wpService != null) {
+                applyComponentAtInstall(wpService, applyToLock);
+                Slog.w(TAG, "Wallpaper service " + wpService + " isn't available. "
+                        + " Will try to apply later");
+            }
+        }
+    }
+
     private void restoreFromStage(File stage, File info, String hintTag, int which)
             throws IOException {
         if (stage.exists()) {
@@ -372,7 +381,8 @@
         return (value == null) ? defValue : Integer.parseInt(value);
     }
 
-    private boolean servicePackageExists(ComponentName comp) {
+    @VisibleForTesting
+    boolean servicePackageExists(ComponentName comp) {
         try {
             if (comp != null) {
                 final IPackageManager pm = AppGlobals.getPackageManager();
@@ -401,4 +411,53 @@
             throws IOException {
         // Intentionally blank
     }
+
+    private void applyComponentAtInstall(ComponentName componentName, boolean applyToLock) {
+        PackageMonitor packageMonitor = getWallpaperPackageMonitor(componentName, applyToLock);
+        packageMonitor.register(getBaseContext(), null, UserHandle.ALL, true);
+    }
+
+    @VisibleForTesting
+    PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, boolean applyToLock) {
+        return new PackageMonitor() {
+            @Override
+            public void onPackageAdded(String packageName, int uid) {
+                if (!isDeviceInRestore()) {
+                    // We don't want to reapply the wallpaper outside a restore.
+                    unregister();
+                    return;
+                }
+
+                if (componentName.getPackageName().equals(packageName)) {
+                    Slog.d(TAG, "Applying component " + componentName);
+                    mWm.setWallpaperComponent(componentName);
+                    if (applyToLock) {
+                        try {
+                            mWm.clear(FLAG_LOCK);
+                        } catch (IOException e) {
+                            Slog.w(TAG, "Failed to apply live wallpaper to lock screen: " + e);
+                        }
+                    }
+                    // We're only expecting to restore the wallpaper component once.
+                    unregister();
+                }
+            }
+        };
+    }
+
+    @VisibleForTesting
+    boolean isDeviceInRestore() {
+        try {
+            boolean isInSetup = Settings.Secure.getInt(getBaseContext().getContentResolver(),
+                    Settings.Secure.USER_SETUP_COMPLETE) == 0;
+            boolean isInDeferredSetup = Settings.Secure.getInt(getBaseContext()
+                            .getContentResolver(),
+                    Settings.Secure.USER_SETUP_PERSONALIZATION_STATE) ==
+                    Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED;
+            return isInSetup || isInDeferredSetup;
+        } catch (Settings.SettingNotFoundException e) {
+            Slog.w(TAG, "Failed to check if the user is in restore: " + e);
+            return false;
+        }
+    }
 }
\ No newline at end of file
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/tests/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
similarity index 65%
rename from packages/WallpaperBackup/test/src/com/android/wallpaperbackup/tests/WallpaperBackupAgentTest.java
rename to packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index 255fdef..4367075 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/tests/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wallpaperbackup.tests;
+package com.android.wallpaperbackup;
 
 import static android.app.WallpaperManager.FLAG_LOCK;
 import static android.app.WallpaperManager.FLAG_SYSTEM;
@@ -26,17 +26,22 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.WallpaperManager;
 import android.app.backup.FullBackupDataOutput;
+import android.content.ComponentName;
+import android.content.Context;
 import android.content.SharedPreferences;
 import android.os.UserHandle;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.content.PackageMonitor;
 import com.android.wallpaperbackup.WallpaperBackupAgent;
 import com.android.wallpaperbackup.utils.ContextWithServiceOverrides;
 
@@ -58,6 +63,7 @@
 public class WallpaperBackupAgentTest {
     private static final String SYSTEM_GENERATION = "system_gen";
     private static final String LOCK_GENERATION = "lock_gen";
+    private static final String TEST_WALLPAPER_PACKAGE = "wallpaper_package";
 
     private static final int TEST_SYSTEM_WALLPAPER_ID = 1;
     private static final int TEST_LOCK_WALLPAPER_ID = 2;
@@ -66,11 +72,13 @@
     @Mock private WallpaperManager mWallpaperManager;
     @Mock private SharedPreferences mSharedPreferences;
     @Mock private SharedPreferences.Editor mSharedPreferenceEditor;
+    @Mock private Context mMockContext;
 
     @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
 
     private ContextWithServiceOverrides mContext;
     private IsolatedWallpaperBackupAgent mWallpaperBackupAgent;
+    private ComponentName mWallpaperComponent;
 
     @Before
     public void setUp() {
@@ -88,6 +96,8 @@
         mWallpaperBackupAgent = new IsolatedWallpaperBackupAgent(mTemporaryFolder.getRoot());
         mWallpaperBackupAgent.attach(mContext);
         mWallpaperBackupAgent.onCreate();
+
+        mWallpaperComponent = new ComponentName(TEST_WALLPAPER_PACKAGE, "");
     }
 
     @Test
@@ -130,6 +140,69 @@
         inOrder.verify(mSharedPreferenceEditor).apply();
     }
 
+    @Test
+    public void updateWallpaperComponent_doesApplyLater() throws IOException {
+        mWallpaperBackupAgent.mIsDeviceInRestore = true;
+
+        mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+                /* applyToLock */ true);
+
+        // Imitate wallpaper component installation.
+        mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
+                /* uid */0);
+
+        verify(mWallpaperManager, times(1)).setWallpaperComponent(mWallpaperComponent);
+        verify(mWallpaperManager, times(1)).clear(eq(FLAG_LOCK));
+    }
+
+    @Test
+    public void updateWallpaperComponent_applyToLockFalse_doesApplyLaterOnlyToMainScreen()
+            throws IOException {
+        mWallpaperBackupAgent.mIsDeviceInRestore = true;
+
+        mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+                /* applyToLock */ false);
+
+        // Imitate wallpaper component installation.
+        mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
+                /* uid */0);
+
+        verify(mWallpaperManager, times(1)).setWallpaperComponent(mWallpaperComponent);
+        verify(mWallpaperManager, never()).clear(eq(FLAG_LOCK));
+    }
+
+    @Test
+    public void updateWallpaperComponent_deviceNotInRestore_doesNotApply()
+            throws IOException {
+        mWallpaperBackupAgent.mIsDeviceInRestore = false;
+
+        mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+                /* applyToLock */ true);
+
+        // Imitate wallpaper component installation.
+        mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
+                /* uid */0);
+
+        verify(mWallpaperManager, never()).setWallpaperComponent(mWallpaperComponent);
+        verify(mWallpaperManager, never()).clear(eq(FLAG_LOCK));
+    }
+
+    @Test
+    public void updateWallpaperComponent_differentPackageInstalled_doesNotApply()
+            throws IOException {
+        mWallpaperBackupAgent.mIsDeviceInRestore = false;
+
+        mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+                /* applyToLock */ true);
+
+        // Imitate "wrong" wallpaper component installation.
+        mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(/* packageName */"",
+                /* uid */0);
+
+        verify(mWallpaperManager, never()).setWallpaperComponent(mWallpaperComponent);
+        verify(mWallpaperManager, never()).clear(eq(FLAG_LOCK));
+    }
+
     private void mockUnbackedUpState() {
         mockCurrentWallpapers(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
         when(mSharedPreferences.getInt(eq(SYSTEM_GENERATION), eq(-1))).thenReturn(-1);
@@ -162,6 +235,8 @@
     private class IsolatedWallpaperBackupAgent extends WallpaperBackupAgent {
         File mWallpaperBaseDirectory;
         List<File> mBackedUpFiles = new ArrayList<>();
+        PackageMonitor mWallpaperPackageMonitor;
+        boolean mIsDeviceInRestore = false;
 
         IsolatedWallpaperBackupAgent(File wallpaperBaseDirectory) {
             mWallpaperBaseDirectory = wallpaperBaseDirectory;
@@ -181,5 +256,27 @@
         public SharedPreferences getSharedPreferences(File file, int mode) {
             return mSharedPreferences;
         }
+
+        @Override
+        boolean servicePackageExists(ComponentName comp) {
+            return false;
+        }
+
+        @Override
+        boolean isDeviceInRestore() {
+            return mIsDeviceInRestore;
+        }
+
+        @Override
+        PackageMonitor getWallpaperPackageMonitor(ComponentName componentName,
+                boolean applyToLock) {
+            mWallpaperPackageMonitor = super.getWallpaperPackageMonitor(componentName, applyToLock);
+            return mWallpaperPackageMonitor;
+        }
+
+        @Override
+        public Context getBaseContext() {
+            return mMockContext;
+        }
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
index ff59c24..20a11bd 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
@@ -17,6 +17,7 @@
 package com.android.server.accessibility;
 
 import android.annotation.NonNull;
+import android.app.ActivityManager;
 import android.os.ShellCommand;
 import android.os.UserHandle;
 
@@ -83,7 +84,7 @@
                 return null;
             }
         }
-        return UserHandle.USER_SYSTEM;
+        return ActivityManager.getCurrentUser();
     }
 
     @Override
diff --git a/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java
new file mode 100644
index 0000000..3612e09
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2020 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.autofill;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.autofill.AutofillId;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import android.view.inputmethod.InlineSuggestionsResponse;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.inputmethod.InputMethodManagerInternal;
+
+import java.util.Collections;
+import java.util.Optional;
+import java.util.function.Consumer;
+
+
+/**
+ * Controls the interaction with the IME for the inline suggestion sessions.
+ */
+final class AutofillInlineSessionController {
+    @NonNull
+    private final InputMethodManagerInternal mInputMethodManagerInternal;
+    private final int mUserId;
+    @NonNull
+    private final ComponentName mComponentName;
+    @NonNull
+    private final Object mLock;
+    @NonNull
+    private final Handler mHandler;
+
+    @GuardedBy("mLock")
+    private AutofillInlineSuggestionsRequestSession mSession;
+
+    AutofillInlineSessionController(InputMethodManagerInternal inputMethodManagerInternal,
+            int userId, ComponentName componentName, Handler handler, Object lock) {
+        mInputMethodManagerInternal = inputMethodManagerInternal;
+        mUserId = userId;
+        mComponentName = componentName;
+        mHandler = handler;
+        mLock = lock;
+    }
+
+
+    /**
+     * Requests the IME to create an {@link InlineSuggestionsRequest} for {@code autofillId}.
+     *
+     * @param autofillId      the Id of the field for which the request is for.
+     * @param requestConsumer the callback which will be invoked when IME responded or if it times
+     *                        out waiting for IME response.
+     */
+    @GuardedBy("mLock")
+    void onCreateInlineSuggestionsRequestLocked(@NonNull AutofillId autofillId,
+            @NonNull Consumer<InlineSuggestionsRequest> requestConsumer, @NonNull Bundle uiExtras) {
+        // TODO(b/151123764): rename the method to better reflect what it does.
+        if (mSession != null) {
+            // Send an empty response to IME and destroy the existing session.
+            mSession.onInlineSuggestionsResponseLocked(mSession.getAutofillIdLocked(),
+                    new InlineSuggestionsResponse(Collections.EMPTY_LIST));
+            mSession.destroySessionLocked();
+        }
+        // TODO(b/151123764): consider reusing the same AutofillInlineSession object for the
+        // same field.
+        mSession = new AutofillInlineSuggestionsRequestSession(mInputMethodManagerInternal, mUserId,
+                mComponentName, mHandler, mLock, autofillId, requestConsumer, uiExtras);
+        mSession.onCreateInlineSuggestionsRequestLocked();
+
+    }
+
+    /**
+     * Returns the {@link InlineSuggestionsRequest} provided by IME for the last request.
+     *
+     * <p> The caller is responsible for making sure Autofill hears back from IME before calling
+     * this method, using the {@code requestConsumer} provided when calling {@link
+     * #onCreateInlineSuggestionsRequestLocked(AutofillId, Consumer, Bundle)}.
+     */
+    @GuardedBy("mLock")
+    Optional<InlineSuggestionsRequest> getInlineSuggestionsRequestLocked() {
+        if (mSession != null) {
+            return mSession.getInlineSuggestionsRequestLocked();
+        }
+        return Optional.empty();
+    }
+
+    /**
+     * Requests the IME to hide the current suggestions, if any. Returns true if the message is sent
+     * to the IME.
+     */
+    @GuardedBy("mLock")
+    boolean hideInlineSuggestionsUiLocked(@NonNull AutofillId autofillId) {
+        if (mSession != null) {
+            return mSession.onInlineSuggestionsResponseLocked(autofillId,
+                    new InlineSuggestionsResponse(Collections.EMPTY_LIST));
+        }
+        return false;
+    }
+
+    /**
+     * Requests showing the inline suggestion in the IME when the IME becomes visible and is focused
+     * on the {@code autofillId}.
+     *
+     * @return false if there is no session, or if the IME callback is not available in the session.
+     */
+    @GuardedBy("mLock")
+    boolean onInlineSuggestionsResponseLocked(@NonNull AutofillId autofillId,
+            @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) {
+        // TODO(b/151123764): rename the method to better reflect what it does.
+        if (mSession != null) {
+            return mSession.onInlineSuggestionsResponseLocked(autofillId,
+                    inlineSuggestionsResponse);
+        }
+        return false;
+    }
+}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
new file mode 100644
index 0000000..ca230b6
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2020 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.autofill;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import static com.android.server.autofill.Helper.sDebug;
+
+import android.annotation.BinderThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+import android.view.autofill.AutofillId;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import android.view.inputmethod.InlineSuggestionsResponse;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
+import com.android.internal.view.IInlineSuggestionsResponseCallback;
+import com.android.internal.view.InlineSuggestionsRequestInfo;
+import com.android.server.inputmethod.InputMethodManagerInternal;
+
+import java.lang.ref.WeakReference;
+import java.util.Optional;
+import java.util.function.Consumer;
+
+/**
+ * Maintains an inline suggestion session with the IME.
+ *
+ * <p> Each session corresponds to one request from the Autofill manager service to create an
+ * {@link InlineSuggestionsRequest}. It's responsible for receiving callbacks from the IME and
+ * sending {@link android.view.inputmethod.InlineSuggestionsResponse} to IME.
+ */
+final class AutofillInlineSuggestionsRequestSession {
+
+    private static final String TAG = AutofillInlineSuggestionsRequestSession.class.getSimpleName();
+    private static final int INLINE_REQUEST_TIMEOUT_MS = 200;
+
+    @NonNull
+    private final InputMethodManagerInternal mInputMethodManagerInternal;
+    private final int mUserId;
+    @NonNull
+    private final ComponentName mComponentName;
+    @NonNull
+    private final Object mLock;
+    @NonNull
+    private final Handler mHandler;
+    @NonNull
+    private final Bundle mUiExtras;
+
+    @GuardedBy("mLock")
+    @NonNull
+    private AutofillId mAutofillId;
+    @GuardedBy("mLock")
+    @Nullable
+    private Consumer<InlineSuggestionsRequest> mImeRequestConsumer;
+
+    @GuardedBy("mLock")
+    private boolean mImeRequestReceived;
+    @GuardedBy("mLock")
+    @Nullable
+    private InlineSuggestionsRequest mImeRequest;
+    @GuardedBy("mLock")
+    @Nullable
+    private IInlineSuggestionsResponseCallback mResponseCallback;
+    @GuardedBy("mLock")
+    @Nullable
+    private Runnable mTimeoutCallback;
+
+    @GuardedBy("mLock")
+    @Nullable
+    private AutofillId mImeCurrentFieldId;
+    @GuardedBy("mLock")
+    private boolean mImeInputStarted;
+    @GuardedBy("mLock")
+    private boolean mImeInputViewStarted;
+    @GuardedBy("mLock")
+    @Nullable
+    private InlineSuggestionsResponse mInlineSuggestionsResponse;
+    @GuardedBy("mLock")
+    private boolean mPreviousResponseIsNotEmpty;
+
+    @GuardedBy("mLock")
+    private boolean mDestroyed = false;
+
+    AutofillInlineSuggestionsRequestSession(
+            @NonNull InputMethodManagerInternal inputMethodManagerInternal, int userId,
+            @NonNull ComponentName componentName, @NonNull Handler handler, @NonNull Object lock,
+            @NonNull AutofillId autofillId,
+            @NonNull Consumer<InlineSuggestionsRequest> requestConsumer, @NonNull Bundle uiExtras) {
+        mInputMethodManagerInternal = inputMethodManagerInternal;
+        mUserId = userId;
+        mComponentName = componentName;
+        mHandler = handler;
+        mLock = lock;
+        mUiExtras = uiExtras;
+
+        mAutofillId = autofillId;
+        mImeRequestConsumer = requestConsumer;
+    }
+
+    @GuardedBy("mLock")
+    @NonNull
+    AutofillId getAutofillIdLocked() {
+        return mAutofillId;
+    }
+
+    /**
+     * Returns the {@link InlineSuggestionsRequest} provided by IME.
+     *
+     * <p> The caller is responsible for making sure Autofill hears back from IME before calling
+     * this method, using the {@link #mImeRequestConsumer}.
+     */
+    @GuardedBy("mLock")
+    Optional<InlineSuggestionsRequest> getInlineSuggestionsRequestLocked() {
+        if (mDestroyed) {
+            return Optional.empty();
+        }
+        return Optional.ofNullable(mImeRequest);
+    }
+
+    /**
+     * Requests showing the inline suggestion in the IME when the IME becomes visible and is focused
+     * on the {@code autofillId}.
+     *
+     * @return false if the IME callback is not available.
+     */
+    @GuardedBy("mLock")
+    boolean onInlineSuggestionsResponseLocked(@NonNull AutofillId autofillId,
+            @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) {
+        if (mDestroyed) {
+            return false;
+        }
+        if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked called for:" + autofillId);
+        if (mImeRequest == null || mResponseCallback == null) {
+            return false;
+        }
+        // TODO(b/151123764): each session should only correspond to one field.
+        mAutofillId = autofillId;
+        mInlineSuggestionsResponse = inlineSuggestionsResponse;
+        maybeUpdateResponseToImeLocked();
+        return true;
+    }
+
+    /**
+     * This method must be called when the session is destroyed, to avoid further callbacks from/to
+     * the IME.
+     */
+    @GuardedBy("mLock")
+    void destroySessionLocked() {
+        mDestroyed = true;
+    }
+
+    /**
+     * Requests the IME to create an {@link InlineSuggestionsRequest}.
+     *
+     * <p> This method should only be called once per session.
+     */
+    @GuardedBy("mLock")
+    void onCreateInlineSuggestionsRequestLocked() {
+        if (sDebug) Log.d(TAG, "onCreateInlineSuggestionsRequestLocked called: " + mAutofillId);
+        if (mDestroyed) {
+            return;
+        }
+        mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(mUserId,
+                new InlineSuggestionsRequestInfo(mComponentName, mAutofillId, mUiExtras),
+                new InlineSuggestionsRequestCallbackImpl(this));
+        mTimeoutCallback = () -> {
+            Log.w(TAG, "Timed out waiting for IME callback InlineSuggestionsRequest.");
+            handleOnReceiveImeRequest(null, null);
+        };
+        mHandler.postDelayed(mTimeoutCallback, INLINE_REQUEST_TIMEOUT_MS);
+    }
+
+    /**
+     * Optionally sends inline response to the IME, depending on the current state.
+     */
+    @GuardedBy("mLock")
+    private void maybeUpdateResponseToImeLocked() {
+        if (sDebug) Log.d(TAG, "maybeUpdateResponseToImeLocked called");
+        if (mDestroyed || mResponseCallback == null) {
+            return;
+        }
+        if (!mImeInputViewStarted && mPreviousResponseIsNotEmpty) {
+            // 1. if previous response is not empty, and IME just become invisible, then send
+            // empty response to make sure existing responses don't stick around on the IME.
+            // Although the inline suggestions should disappear when IME hides which removes them
+            // from the view hierarchy, but we still send an empty response to be extra safe.
+
+            // TODO(b/149945531): clear the existing suggestions when IME is hide, once the bug is
+            //  fixed.
+            //if (sDebug) Log.d(TAG, "Send empty inline response");
+            //updateResponseToImeUncheckLocked(new InlineSuggestionsResponse(Collections
+            // .EMPTY_LIST));
+            //mPreviousResponseIsNotEmpty = false;
+        } else if (mImeInputViewStarted && mInlineSuggestionsResponse != null && match(mAutofillId,
+                mImeCurrentFieldId)) {
+            // 2. if IME is visible, and response is not null, send the response
+            boolean isEmptyResponse = mInlineSuggestionsResponse.getInlineSuggestions().isEmpty();
+            if (isEmptyResponse && !mPreviousResponseIsNotEmpty) {
+                // No-op if both the previous response and current response are empty.
+                return;
+            }
+            if (sDebug) {
+                Log.d(TAG, "Send inline response: "
+                        + mInlineSuggestionsResponse.getInlineSuggestions().size());
+            }
+            updateResponseToImeUncheckLocked(mInlineSuggestionsResponse);
+            // TODO(b/149945531): don't set the response to null so it's cached, once the bug is
+            //  fixed.
+            mInlineSuggestionsResponse = null;
+            mPreviousResponseIsNotEmpty = !isEmptyResponse;
+        }
+    }
+
+    /**
+     * Sends the {@code response} to the IME, assuming all the relevant checks are already done.
+     */
+    @GuardedBy("mLock")
+    private void updateResponseToImeUncheckLocked(InlineSuggestionsResponse response) {
+        if (mDestroyed) {
+            return;
+        }
+        try {
+            mResponseCallback.onInlineSuggestionsResponse(mAutofillId, response);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "RemoteException sending InlineSuggestionsResponse to IME");
+        }
+    }
+
+    /**
+     * Handles the {@code request} and {@code callback} received from the IME.
+     *
+     * <p> Should only invoked in the {@link #mHandler} thread.
+     */
+    private void handleOnReceiveImeRequest(@Nullable InlineSuggestionsRequest request,
+            @Nullable IInlineSuggestionsResponseCallback callback) {
+        synchronized (mLock) {
+            if (mDestroyed || mImeRequestReceived) {
+                return;
+            }
+            mImeRequestReceived = true;
+
+            if (mTimeoutCallback != null) {
+                if (sDebug) Log.d(TAG, "removing timeout callback");
+                mHandler.removeCallbacks(mTimeoutCallback);
+                mTimeoutCallback = null;
+            }
+            if (request != null && callback != null) {
+                mImeRequest = request;
+                mResponseCallback = callback;
+                handleOnReceiveImeStatusUpdated(mAutofillId, true, false);
+            }
+            if (mImeRequestConsumer != null) {
+                // Note that mImeRequest is only set if both request and callback are non-null.
+                mImeRequestConsumer.accept(mImeRequest);
+                mImeRequestConsumer = null;
+            }
+        }
+    }
+
+    /**
+     * Handles the IME status updates received from the IME.
+     *
+     * <p> Should only be invoked in the {@link #mHandler} thread.
+     */
+    private void handleOnReceiveImeStatusUpdated(boolean imeInputStarted,
+            boolean imeInputViewStarted) {
+        synchronized (mLock) {
+            if (mDestroyed) {
+                return;
+            }
+            if (mImeCurrentFieldId != null) {
+                boolean imeInputStartedChanged = (mImeInputStarted != imeInputStarted);
+                boolean imeInputViewStartedChanged = (mImeInputViewStarted != imeInputViewStarted);
+                mImeInputStarted = imeInputStarted;
+                mImeInputViewStarted = imeInputViewStarted;
+                if (imeInputStartedChanged || imeInputViewStartedChanged) {
+                    maybeUpdateResponseToImeLocked();
+                }
+            }
+        }
+    }
+
+    /**
+     * Handles the IME status updates received from the IME.
+     *
+     * <p> Should only be invoked in the {@link #mHandler} thread.
+     */
+    private void handleOnReceiveImeStatusUpdated(@Nullable AutofillId imeFieldId,
+            boolean imeInputStarted, boolean imeInputViewStarted) {
+        synchronized (mLock) {
+            if (mDestroyed) {
+                return;
+            }
+            if (imeFieldId != null) {
+                mImeCurrentFieldId = imeFieldId;
+            }
+            handleOnReceiveImeStatusUpdated(imeInputStarted, imeInputViewStarted);
+        }
+    }
+
+    private static final class InlineSuggestionsRequestCallbackImpl extends
+            IInlineSuggestionsRequestCallback.Stub {
+
+        private final WeakReference<AutofillInlineSuggestionsRequestSession> mSession;
+
+        private InlineSuggestionsRequestCallbackImpl(
+                AutofillInlineSuggestionsRequestSession session) {
+            mSession = new WeakReference<>(session);
+        }
+
+        @BinderThread
+        @Override
+        public void onInlineSuggestionsUnsupported() throws RemoteException {
+            if (sDebug) Log.d(TAG, "onInlineSuggestionsUnsupported() called.");
+            final AutofillInlineSuggestionsRequestSession session = mSession.get();
+            if (session != null) {
+                session.mHandler.sendMessage(obtainMessage(
+                        AutofillInlineSuggestionsRequestSession::handleOnReceiveImeRequest, session,
+                        null, null));
+            }
+        }
+
+        @BinderThread
+        @Override
+        public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
+                IInlineSuggestionsResponseCallback callback) {
+            if (sDebug) Log.d(TAG, "onInlineSuggestionsRequest() received: " + request);
+            final AutofillInlineSuggestionsRequestSession session = mSession.get();
+            if (session != null) {
+                session.mHandler.sendMessage(obtainMessage(
+                        AutofillInlineSuggestionsRequestSession::handleOnReceiveImeRequest, session,
+                        request, callback));
+            }
+        }
+
+        @Override
+        public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException {
+            if (sDebug) Log.d(TAG, "onInputMethodStartInput() received on " + imeFieldId);
+            final AutofillInlineSuggestionsRequestSession session = mSession.get();
+            if (session != null) {
+                session.mHandler.sendMessage(obtainMessage(
+                        AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated,
+                        session, imeFieldId, true, false));
+            }
+        }
+
+        @Override
+        public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException {
+            if (sDebug) {
+                Log.d(TAG, "onInputMethodShowInputRequested() received: " + requestResult);
+            }
+        }
+
+        @BinderThread
+        @Override
+        public void onInputMethodStartInputView() {
+            if (sDebug) Log.d(TAG, "onInputMethodStartInputView() received");
+            final AutofillInlineSuggestionsRequestSession session = mSession.get();
+            if (session != null) {
+                session.mHandler.sendMessage(obtainMessage(
+                        AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated,
+                        session, true, true));
+            }
+        }
+
+        @BinderThread
+        @Override
+        public void onInputMethodFinishInputView() {
+            if (sDebug) Log.d(TAG, "onInputMethodFinishInputView() received");
+            final AutofillInlineSuggestionsRequestSession session = mSession.get();
+            if (session != null) {
+                session.mHandler.sendMessage(obtainMessage(
+                        AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated,
+                        session, true, false));
+            }
+        }
+
+        @Override
+        public void onInputMethodFinishInput() throws RemoteException {
+            if (sDebug) Log.d(TAG, "onInputMethodFinishInput() received");
+            final AutofillInlineSuggestionsRequestSession session = mSession.get();
+            if (session != null) {
+                session.mHandler.sendMessage(obtainMessage(
+                        AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated,
+                        session, false, false));
+            }
+        }
+    }
+
+    private static boolean match(@Nullable AutofillId autofillId,
+            @Nullable AutofillId imeClientFieldId) {
+        // The IME doesn't have information about the virtual view id for the child views in the
+        // web view, so we are only comparing the parent view id here. This means that for cases
+        // where there are two input fields in the web view, they will have the same view id
+        // (although different virtual child id), and we will not be able to distinguish them.
+        return autofillId != null && imeClientFieldId != null
+                && autofillId.getViewId() == imeClientFieldId.getViewId();
+    }
+}
diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java
deleted file mode 100644
index e2d5112..0000000
--- a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java
+++ /dev/null
@@ -1,403 +0,0 @@
-/*
- * Copyright (C) 2020 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.autofill;
-
-import static com.android.server.autofill.Helper.sDebug;
-
-import android.annotation.BinderThread;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.util.Log;
-import android.util.Slog;
-import android.view.autofill.AutofillId;
-import android.view.inputmethod.InlineSuggestionsRequest;
-import android.view.inputmethod.InlineSuggestionsResponse;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.view.IInlineSuggestionsRequestCallback;
-import com.android.internal.view.IInlineSuggestionsResponseCallback;
-import com.android.internal.view.InlineSuggestionsRequestInfo;
-import com.android.server.inputmethod.InputMethodManagerInternal;
-
-import java.util.Collections;
-import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
-import java.util.function.Consumer;
-
-/**
- * Maintains an autofill inline suggestion session that communicates with the IME.
- *
- * <p>
- * The same session may be reused for multiple input fields involved in the same autofill
- * {@link Session}. Therefore, one {@link InlineSuggestionsRequest} and one
- * {@link IInlineSuggestionsResponseCallback} may be used to generate and callback with inline
- * suggestions for different input fields.
- *
- * <p>
- * This class is the sole place in Autofill responsible for directly communicating with the IME. It
- * receives the IME input view start/finish events, with the associated IME field Id. It uses the
- * information to decide when to send the {@link InlineSuggestionsResponse} to IME. As a result,
- * some of the response will be cached locally and only be sent when the IME is ready to show them.
- *
- * <p>
- * See {@link android.inputmethodservice.InlineSuggestionSession} comments for InputMethodService
- * side flow.
- *
- * <p>
- * This class should hold the same lock as {@link Session} as they call into each other.
- */
-final class InlineSuggestionSession {
-
-    private static final String TAG = "AfInlineSuggestionSession";
-    private static final int INLINE_REQUEST_TIMEOUT_MS = 200;
-
-    @NonNull
-    private final InputMethodManagerInternal mInputMethodManagerInternal;
-    private final int mUserId;
-    @NonNull
-    private final ComponentName mComponentName;
-    @NonNull
-    private final Object mLock;
-    @NonNull
-    private final ImeStatusListener mImeStatusListener;
-    @NonNull
-    private final Handler mHandler;
-
-    /**
-     * To avoid the race condition, one should not access {@code mPendingImeResponse} without
-     * holding the {@code mLock}. For consuming the existing value, tt's recommended to use
-     * {@link #getPendingImeResponse()} to get a copy of the reference to avoid blocking call.
-     */
-    @GuardedBy("mLock")
-    @Nullable
-    private CompletableFuture<ImeResponse> mPendingImeResponse;
-
-    @GuardedBy("mLock")
-    @Nullable
-    private AutofillResponse mPendingAutofillResponse;
-
-    @GuardedBy("mLock")
-    private boolean mIsLastResponseNonEmpty = false;
-
-    @Nullable
-    @GuardedBy("mLock")
-    private AutofillId mImeFieldId = null;
-
-    @GuardedBy("mLock")
-    private boolean mImeInputViewStarted = false;
-
-    InlineSuggestionSession(InputMethodManagerInternal inputMethodManagerInternal,
-            int userId, ComponentName componentName, Handler handler, Object lock) {
-        mInputMethodManagerInternal = inputMethodManagerInternal;
-        mUserId = userId;
-        mComponentName = componentName;
-        mHandler = handler;
-        mLock = lock;
-        mImeStatusListener = new ImeStatusListener() {
-            @Override
-            public void onInputMethodStartInput(AutofillId imeFieldId) {
-                synchronized (mLock) {
-                    mImeFieldId = imeFieldId;
-                    mImeInputViewStarted = false;
-                }
-            }
-
-            @Override
-            public void onInputMethodStartInputView() {
-                synchronized (mLock) {
-                    mImeInputViewStarted = true;
-                    AutofillResponse pendingAutofillResponse = mPendingAutofillResponse;
-                    if (pendingAutofillResponse != null
-                            && pendingAutofillResponse.mAutofillId.equalsIgnoreSession(
-                            mImeFieldId)) {
-                        mPendingAutofillResponse = null;
-                        onInlineSuggestionsResponseLocked(pendingAutofillResponse.mAutofillId,
-                                pendingAutofillResponse.mResponse);
-                    }
-                }
-            }
-
-            @Override
-            public void onInputMethodFinishInputView() {
-                synchronized (mLock) {
-                    mImeInputViewStarted = false;
-                }
-            }
-
-            @Override
-            public void onInputMethodFinishInput() {
-                synchronized (mLock) {
-                    mImeFieldId = null;
-                }
-            }
-        };
-    }
-
-    public void onCreateInlineSuggestionsRequest(@NonNull AutofillId autofillId,
-            @NonNull Consumer<InlineSuggestionsRequest> requestConsumer) {
-        if (sDebug) Log.d(TAG, "onCreateInlineSuggestionsRequest called for " + autofillId);
-
-        synchronized (mLock) {
-            // Clean up all the state about the previous request.
-            hideInlineSuggestionsUi(autofillId);
-            mImeFieldId = null;
-            mImeInputViewStarted = false;
-            if (mPendingImeResponse != null && !mPendingImeResponse.isDone()) {
-                mPendingImeResponse.complete(null);
-            }
-            mPendingImeResponse = new CompletableFuture<>();
-            // TODO(b/146454892): pipe the uiExtras from the ExtServices.
-            mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(
-                    mUserId,
-                    new InlineSuggestionsRequestInfo(mComponentName, autofillId, new Bundle()),
-                    new InlineSuggestionsRequestCallbackImpl(autofillId, mPendingImeResponse,
-                            mImeStatusListener, requestConsumer, mHandler, mLock));
-        }
-    }
-
-    public Optional<InlineSuggestionsRequest> getInlineSuggestionsRequest() {
-        final CompletableFuture<ImeResponse> pendingImeResponse = getPendingImeResponse();
-        if (pendingImeResponse == null || !pendingImeResponse.isDone()) {
-            return Optional.empty();
-        }
-        return Optional.ofNullable(pendingImeResponse.getNow(null)).map(ImeResponse::getRequest);
-    }
-
-    public boolean hideInlineSuggestionsUi(@NonNull AutofillId autofillId) {
-        synchronized (mLock) {
-            if (mIsLastResponseNonEmpty) {
-                return onInlineSuggestionsResponseLocked(autofillId,
-                        new InlineSuggestionsResponse(Collections.EMPTY_LIST));
-            }
-            return false;
-        }
-    }
-
-    public boolean onInlineSuggestionsResponse(@NonNull AutofillId autofillId,
-            @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) {
-        synchronized (mLock) {
-            return onInlineSuggestionsResponseLocked(autofillId, inlineSuggestionsResponse);
-        }
-    }
-
-    private boolean onInlineSuggestionsResponseLocked(@NonNull AutofillId autofillId,
-            @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) {
-        final CompletableFuture<ImeResponse> completedImsResponse = getPendingImeResponse();
-        if (completedImsResponse == null || !completedImsResponse.isDone()) {
-            if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked without IMS request");
-            return false;
-        }
-        // There is no need to wait on the CompletableFuture since it should have been completed.
-        ImeResponse imeResponse = completedImsResponse.getNow(null);
-        if (imeResponse == null) {
-            if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked with pending IMS response");
-            return false;
-        }
-
-        // TODO(b/151846600): IME doesn't have access to the virtual id of the webview, so we
-        //  only compare the view id for now.
-        if (!mImeInputViewStarted || mImeFieldId == null
-                || autofillId.getViewId() != mImeFieldId.getViewId()) {
-            if (sDebug) {
-                Log.d(TAG,
-                        "onInlineSuggestionsResponseLocked not sent because input view is not "
-                                + "started for " + autofillId);
-            }
-            mPendingAutofillResponse = new AutofillResponse(autofillId, inlineSuggestionsResponse);
-            // TODO(b/149442582): Although we are not sending the response to IME right away, we
-            //  still return true to indicate that the response may be sent eventually, such that
-            //  the dropdown UI will not be shown. This may not be the desired behavior in the
-            //  auto-focus case where IME isn't shown after switching back to an activity. We may
-            //  revisit this.
-            return true;
-        }
-
-        try {
-            imeResponse.mCallback.onInlineSuggestionsResponse(autofillId,
-                    inlineSuggestionsResponse);
-            mIsLastResponseNonEmpty = !inlineSuggestionsResponse.getInlineSuggestions().isEmpty();
-            if (sDebug) {
-                Log.d(TAG, "Autofill sends inline response to IME: "
-                        + inlineSuggestionsResponse.getInlineSuggestions().size());
-            }
-            return true;
-        } catch (RemoteException e) {
-            Slog.e(TAG, "RemoteException sending InlineSuggestionsResponse to IME");
-            return false;
-        }
-    }
-
-    @Nullable
-    @GuardedBy("mLock")
-    private CompletableFuture<ImeResponse> getPendingImeResponse() {
-        synchronized (mLock) {
-            return mPendingImeResponse;
-        }
-    }
-
-    private static final class InlineSuggestionsRequestCallbackImpl
-            extends IInlineSuggestionsRequestCallback.Stub {
-
-        private final Object mLock;
-        private final AutofillId mAutofillId;
-        @GuardedBy("mLock")
-        private final CompletableFuture<ImeResponse> mResponse;
-        @GuardedBy("mLock")
-        private final Consumer<InlineSuggestionsRequest> mRequestConsumer;
-        private final ImeStatusListener mImeStatusListener;
-        private final Handler mHandler;
-        private final Runnable mTimeoutCallback;
-
-        private InlineSuggestionsRequestCallbackImpl(AutofillId autofillId,
-                CompletableFuture<ImeResponse> response,
-                ImeStatusListener imeStatusListener,
-                Consumer<InlineSuggestionsRequest> requestConsumer,
-                Handler handler, Object lock) {
-            mAutofillId = autofillId;
-            mResponse = response;
-            mImeStatusListener = imeStatusListener;
-            mRequestConsumer = requestConsumer;
-            mLock = lock;
-
-            mHandler = handler;
-            mTimeoutCallback = () -> {
-                Log.w(TAG, "Timed out waiting for IME callback InlineSuggestionsRequest.");
-                completeIfNot(null);
-            };
-            mHandler.postDelayed(mTimeoutCallback, INLINE_REQUEST_TIMEOUT_MS);
-        }
-
-        private void completeIfNot(@Nullable ImeResponse response) {
-            synchronized (mLock) {
-                if (mResponse.isDone()) {
-                    return;
-                }
-                mResponse.complete(response);
-                mRequestConsumer.accept(response == null ? null : response.mRequest);
-                mHandler.removeCallbacks(mTimeoutCallback);
-            }
-        }
-
-        @BinderThread
-        @Override
-        public void onInlineSuggestionsUnsupported() throws RemoteException {
-            if (sDebug) Log.d(TAG, "onInlineSuggestionsUnsupported() called.");
-            completeIfNot(null);
-        }
-
-        @BinderThread
-        @Override
-        public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
-                IInlineSuggestionsResponseCallback callback) {
-            if (sDebug) Log.d(TAG, "onInlineSuggestionsRequest() received: " + request);
-            mImeStatusListener.onInputMethodStartInput(mAutofillId);
-            if (request != null && callback != null) {
-                completeIfNot(new ImeResponse(request, callback));
-            } else {
-                completeIfNot(null);
-            }
-        }
-
-        @Override
-        public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException {
-            if (sDebug) Log.d(TAG, "onInputMethodStartInput() received on " + imeFieldId);
-            mImeStatusListener.onInputMethodStartInput(imeFieldId);
-        }
-
-        @Override
-        public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException {
-            if (sDebug) {
-                Log.d(TAG, "onInputMethodShowInputRequested() received: " + requestResult);
-            }
-            // TODO(b/151123764): use this signal to adjust the timeout on Autofill side waiting for
-            //  IME to show.
-        }
-
-        @BinderThread
-        @Override
-        public void onInputMethodStartInputView() {
-            if (sDebug) Log.d(TAG, "onInputMethodStartInputView() received");
-            mImeStatusListener.onInputMethodStartInputView();
-        }
-
-        @BinderThread
-        @Override
-        public void onInputMethodFinishInputView() {
-            if (sDebug) Log.d(TAG, "onInputMethodFinishInputView() received");
-            mImeStatusListener.onInputMethodFinishInputView();
-        }
-
-        @Override
-        public void onInputMethodFinishInput() throws RemoteException {
-            if (sDebug) Log.d(TAG, "onInputMethodFinishInput() received");
-            mImeStatusListener.onInputMethodFinishInput();
-        }
-    }
-
-    private interface ImeStatusListener {
-        void onInputMethodStartInput(AutofillId imeFieldId);
-
-        void onInputMethodStartInputView();
-
-        void onInputMethodFinishInputView();
-
-        void onInputMethodFinishInput();
-    }
-
-    /**
-     * A data class wrapping Autofill responses for the inline suggestion request.
-     */
-    private static class AutofillResponse {
-        @NonNull
-        final AutofillId mAutofillId;
-
-        @NonNull
-        final InlineSuggestionsResponse mResponse;
-
-        AutofillResponse(@NonNull AutofillId autofillId,
-                @NonNull InlineSuggestionsResponse response) {
-            mAutofillId = autofillId;
-            mResponse = response;
-        }
-
-    }
-
-    /**
-     * A data class wrapping IME responses for the create inline suggestions request.
-     */
-    private static class ImeResponse {
-        @NonNull
-        final InlineSuggestionsRequest mRequest;
-
-        @NonNull
-        final IInlineSuggestionsResponseCallback mCallback;
-
-        ImeResponse(@NonNull InlineSuggestionsRequest request,
-                @NonNull IInlineSuggestionsResponseCallback callback) {
-            mRequest = request;
-            mCallback = callback;
-        }
-
-        InlineSuggestionsRequest getRequest() {
-            return mRequest;
-        }
-    }
-}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 1290569..4ecffd8 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -304,7 +304,7 @@
     private boolean mForAugmentedAutofillOnly;
 
     @Nullable
-    private final InlineSuggestionSession mInlineSuggestionSession;
+    private final AutofillInlineSessionController mInlineSessionController;
 
     /**
      * Receiver of assist data from the app's {@link Activity}.
@@ -720,8 +720,9 @@
             Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer =
                     mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ true);
             if (inlineSuggestionsRequestConsumer != null) {
-                mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId,
-                        inlineSuggestionsRequestConsumer);
+                // TODO(b/146454892): pipe the uiExtras from the ExtServices.
+                mInlineSessionController.onCreateInlineSuggestionsRequestLocked(mCurrentViewId,
+                        inlineSuggestionsRequestConsumer, Bundle.EMPTY);
             }
         } else {
             mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ false);
@@ -777,8 +778,8 @@
         mForAugmentedAutofillOnly = forAugmentedAutofillOnly;
         setClientLocked(client);
 
-        mInlineSuggestionSession = new InlineSuggestionSession(inputMethodManagerInternal, userId,
-                componentName, handler, mLock);
+        mInlineSessionController = new AutofillInlineSessionController(inputMethodManagerInternal,
+                userId, componentName, handler, mLock);
 
         mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED)
                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags));
@@ -2561,7 +2562,7 @@
                     if (sVerbose) Slog.v(TAG, "Exiting view " + id);
                     mUi.hideFillUi(this);
                     hideAugmentedAutofillLocked(viewState);
-                    mInlineSuggestionSession.hideInlineSuggestionsUi(mCurrentViewId);
+                    mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId);
                     mCurrentViewId = null;
                 }
                 break;
@@ -2779,7 +2780,7 @@
     private boolean requestShowInlineSuggestionsLocked(@NonNull FillResponse response,
             @Nullable String filterText) {
         final Optional<InlineSuggestionsRequest> inlineSuggestionsRequest =
-                mInlineSuggestionSession.getInlineSuggestionsRequest();
+                mInlineSessionController.getInlineSuggestionsRequestLocked();
         if (!inlineSuggestionsRequest.isPresent()) {
             Log.w(TAG, "InlineSuggestionsRequest unavailable");
             return false;
@@ -2801,7 +2802,8 @@
                         inlineSuggestionsRequest.get(), response, filterText, mCurrentViewId,
                         this, () -> {
                             synchronized (mLock) {
-                                mInlineSuggestionSession.hideInlineSuggestionsUi(mCurrentViewId);
+                                mInlineSessionController.hideInlineSuggestionsUiLocked(
+                                        mCurrentViewId);
                             }
                         }, remoteRenderService);
         if (inlineSuggestionsResponse == null) {
@@ -2809,7 +2811,7 @@
             return false;
         }
 
-        return mInlineSuggestionSession.onInlineSuggestionsResponse(mCurrentViewId,
+        return mInlineSessionController.onInlineSuggestionsResponseLocked(mCurrentViewId,
                 inlineSuggestionsResponse);
     }
 
@@ -3106,8 +3108,13 @@
                             focusedId,
                             currentValue, inlineSuggestionsRequest,
                             /*inlineSuggestionsCallback=*/
-                            response -> mInlineSuggestionSession.onInlineSuggestionsResponse(
-                                    mCurrentViewId, response),
+                            response -> {
+                                synchronized (mLock) {
+                                    return mInlineSessionController
+                                            .onInlineSuggestionsResponseLocked(
+                                            mCurrentViewId, response);
+                                }
+                            },
                             /*onErrorCallback=*/ () -> {
                                 synchronized (mLock) {
                                     cancelAugmentedAutofillLocked();
@@ -3125,11 +3132,12 @@
                 && (mForAugmentedAutofillOnly
                 || !isInlineSuggestionsEnabledByAutofillProviderLocked())) {
             if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill");
-            mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId,
-                    /*requestConsumer=*/ requestAugmentedAutofill);
+            // TODO(b/146454892): pipe the uiExtras from the ExtServices.
+            mInlineSessionController.onCreateInlineSuggestionsRequestLocked(mCurrentViewId,
+                    /*requestConsumer=*/ requestAugmentedAutofill, Bundle.EMPTY);
         } else {
             requestAugmentedAutofill.accept(
-                    mInlineSuggestionSession.getInlineSuggestionsRequest().orElse(null));
+                    mInlineSessionController.getInlineSuggestionsRequestLocked().orElse(null));
         }
         if (mAugmentedAutofillDestroyer == null) {
             mAugmentedAutofillDestroyer = () -> remoteService.onDestroyAutofillWindowsRequest();
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 192ea72..3c0d880 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -119,8 +119,6 @@
     private static final int MESSAGE_DISABLE = 2;
     private static final int MESSAGE_HANDLE_ENABLE_DELAYED = 3;
     private static final int MESSAGE_HANDLE_DISABLE_DELAYED = 4;
-    private static final int MESSAGE_REGISTER_ADAPTER = 20;
-    private static final int MESSAGE_UNREGISTER_ADAPTER = 21;
     private static final int MESSAGE_REGISTER_STATE_CHANGE_CALLBACK = 30;
     private static final int MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK = 31;
     private static final int MESSAGE_BLUETOOTH_SERVICE_CONNECTED = 40;
@@ -642,10 +640,9 @@
             Slog.w(TAG, "Callback is null in registerAdapter");
             return null;
         }
-        Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_ADAPTER);
-        msg.obj = callback;
-        mHandler.sendMessage(msg);
-
+        synchronized (mCallbacks) {
+            mCallbacks.register(callback);
+        }
         return mBluetooth;
     }
 
@@ -655,9 +652,9 @@
             return;
         }
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_ADAPTER);
-        msg.obj = callback;
-        mHandler.sendMessage(msg);
+        synchronized (mCallbacks) {
+            mCallbacks.unregister(callback);
+        }
     }
 
     public void registerStateChangeCallback(IBluetoothStateChangeCallback callback) {
@@ -1559,18 +1556,20 @@
      * Inform BluetoothAdapter instances that Adapter service is up
      */
     private void sendBluetoothServiceUpCallback() {
-        try {
-            int n = mCallbacks.beginBroadcast();
-            Slog.d(TAG, "Broadcasting onBluetoothServiceUp() to " + n + " receivers.");
-            for (int i = 0; i < n; i++) {
-                try {
-                    mCallbacks.getBroadcastItem(i).onBluetoothServiceUp(mBluetooth);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Unable to call onBluetoothServiceUp() on callback #" + i, e);
+        synchronized (mCallbacks) {
+            try {
+                int n = mCallbacks.beginBroadcast();
+                Slog.d(TAG, "Broadcasting onBluetoothServiceUp() to " + n + " receivers.");
+                for (int i = 0; i < n; i++) {
+                    try {
+                        mCallbacks.getBroadcastItem(i).onBluetoothServiceUp(mBluetooth);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Unable to call onBluetoothServiceUp() on callback #" + i, e);
+                    }
                 }
+            } finally {
+                mCallbacks.finishBroadcast();
             }
-        } finally {
-            mCallbacks.finishBroadcast();
         }
     }
 
@@ -1578,18 +1577,20 @@
      * Inform BluetoothAdapter instances that Adapter service is down
      */
     private void sendBluetoothServiceDownCallback() {
-        try {
-            int n = mCallbacks.beginBroadcast();
-            Slog.d(TAG, "Broadcasting onBluetoothServiceDown() to " + n + " receivers.");
-            for (int i = 0; i < n; i++) {
-                try {
-                    mCallbacks.getBroadcastItem(i).onBluetoothServiceDown();
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Unable to call onBluetoothServiceDown() on callback #" + i, e);
+        synchronized (mCallbacks) {
+            try {
+                int n = mCallbacks.beginBroadcast();
+                Slog.d(TAG, "Broadcasting onBluetoothServiceDown() to " + n + " receivers.");
+                for (int i = 0; i < n; i++) {
+                    try {
+                        mCallbacks.getBroadcastItem(i).onBluetoothServiceDown();
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Unable to call onBluetoothServiceDown() on callback #" + i, e);
+                    }
                 }
+            } finally {
+                mCallbacks.finishBroadcast();
             }
-        } finally {
-            mCallbacks.finishBroadcast();
         }
     }
 
@@ -1917,17 +1918,6 @@
                                 mContext.getPackageName());
                     }
                     break;
-
-                case MESSAGE_REGISTER_ADAPTER: {
-                    IBluetoothManagerCallback callback = (IBluetoothManagerCallback) msg.obj;
-                    mCallbacks.register(callback);
-                    break;
-                }
-                case MESSAGE_UNREGISTER_ADAPTER: {
-                    IBluetoothManagerCallback callback = (IBluetoothManagerCallback) msg.obj;
-                    mCallbacks.unregister(callback);
-                    break;
-                }
                 case MESSAGE_REGISTER_STATE_CHANGE_CALLBACK: {
                     IBluetoothStateChangeCallback callback =
                             (IBluetoothStateChangeCallback) msg.obj;
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index a9c3079..7f25de6b 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -1836,12 +1836,13 @@
 
     @Override
     public void requestLocationUpdates(LocationRequest request, ILocationListener listener,
-            PendingIntent intent, String packageName, String featureId) {
+            PendingIntent intent, String packageName, String featureId, String listenerId) {
         if (request == null) {
             request = DEFAULT_LOCATION_REQUEST;
         }
 
-        CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, featureId);
+        CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, featureId,
+                listenerId);
         identity.enforceLocationPermission();
 
         WorkSource workSource = request.getWorkSource();
@@ -2027,7 +2028,7 @@
     @Override
     public boolean getCurrentLocation(LocationRequest locationRequest,
             ICancellationSignal remoteCancellationSignal, ILocationListener listener,
-            String packageName, String featureId) {
+            String packageName, String featureId, String listenerId) {
         // side effect of validating locationRequest and packageName
         Location lastLocation = getLastLocation(locationRequest, packageName, featureId);
         if (lastLocation != null) {
@@ -2052,7 +2053,7 @@
             }
         }
 
-        requestLocationUpdates(locationRequest, listener, null, packageName, featureId);
+        requestLocationUpdates(locationRequest, listener, null, packageName, featureId, listenerId);
         CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
                 remoteCancellationSignal);
         if (cancellationSignal != null) {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 0671477..9aefc8d 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -4468,9 +4468,8 @@
                             String.format("/storage/emulated/%d/Android/data/%s/",
                                     userId, pkg);
 
-                    int appUid =
-                            UserHandle.getUid(userId, mPmInternal.getPackage(pkg).getUid());
                     // Create package obb and data dir if it doesn't exist.
+                    int appUid = UserHandle.getUid(userId, mPmInternal.getPackage(pkg).getUid());
                     File file = new File(packageObbDir);
                     if (!file.exists()) {
                         vold.setupAppDir(packageObbDir, appUid);
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index dbcb3da..2d6ef81 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -231,7 +231,8 @@
         final ServiceThread adjusterThread =
                 new ServiceThread(TAG, TOP_APP_PRIORITY_BOOST, false /* allowIo */);
         adjusterThread.start();
-        Process.setThreadGroupAndCpuset(adjusterThread.getThreadId(), THREAD_GROUP_TOP_APP);
+        adjusterThread.getThreadHandler().post(() -> Process.setThreadGroupAndCpuset(
+                adjusterThread.getThreadId(), THREAD_GROUP_TOP_APP));
         return adjusterThread;
     }
 
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 595275d..786e9cf 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -97,6 +97,7 @@
 import android.system.Os;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.EventLog;
 import android.util.LongSparseArray;
 import android.util.Pair;
@@ -137,6 +138,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Activity manager code dealing with processes.
@@ -1846,11 +1848,13 @@
                 runtimeFlags |= Zygote.USE_APP_IMAGE_STARTUP_CACHE;
             }
 
-            // Enable heap pointer tagging, unless disabled by the app manifest, target sdk level,
-            // or the compat feature.
-            if (app.info.allowsNativeHeapPointerTagging()
-                    && mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING, app.info)) {
-                runtimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI;
+            if (Zygote.nativeSupportsTaggedPointers()) {
+                // Enable heap pointer tagging if supported by the kernel, unless disabled by the
+                // app manifest, target sdk level, or compat feature.
+                if (app.info.allowsNativeHeapPointerTagging()
+                        && mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING, app.info)) {
+                    runtimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI;
+                }
             }
 
             runtimeFlags |= decideGwpAsanLevel(app);
@@ -2127,18 +2131,11 @@
         for (String packageName : packages) {
             String volumeUuid = pmInt.getPackage(packageName).getVolumeUuid();
             long inode = pmInt.getCeDataInode(packageName, userId);
-            if (inode != 0) {
-                result.put(packageName, Pair.create(volumeUuid, inode));
+            if (inode == 0) {
+                Slog.w(TAG, packageName + " inode == 0 (b/152760674)");
+                return null;
             }
-        }
-        if (mAppDataIsolationWhitelistedApps != null) {
-            for (String packageName : mAppDataIsolationWhitelistedApps) {
-                String volumeUuid = pmInt.getPackage(packageName).getVolumeUuid();
-                long inode = pmInt.getCeDataInode(packageName, userId);
-                if (inode != 0) {
-                    result.put(packageName, Pair.create(volumeUuid, inode));
-                }
-            }
+            result.put(packageName, Pair.create(volumeUuid, inode));
         }
 
         return result;
@@ -2160,35 +2157,56 @@
                 app.setHasForegroundActivities(true);
             }
 
+            final Map<String, Pair<String, Long>> pkgDataInfoMap;
+            final Map<String, Pair<String, Long>> whitelistedAppDataInfoMap;
+            boolean bindMountAppStorageDirs = false;
+            boolean bindMountAppsData = mAppDataIsolationEnabled
+                    && UserHandle.isApp(app.uid)
+                    && mPlatformCompat.isChangeEnabled(APP_DATA_DIRECTORY_ISOLATION, app.info);
+
+            // Get all packages belongs to the same shared uid. sharedPackages is empty array
+            // if it doesn't have shared uid.
+            final PackageManagerInternal pmInt = mService.getPackageManagerInternalLocked();
+            final String[] sharedPackages = pmInt.getSharedUserPackagesForPackage(
+                    app.info.packageName, app.userId);
+            final String[] targetPackagesList = sharedPackages.length == 0
+                    ? new String[]{app.info.packageName} : sharedPackages;
+
+            pkgDataInfoMap = getPackageAppDataInfoMap(pmInt, targetPackagesList, uid);
+            if (pkgDataInfoMap == null) {
+                // TODO(b/152760674): Handle inode == 0 case properly, now we just give it a
+                // tmp free pass.
+                bindMountAppsData = false;
+            }
+
+            // Remove all packages in pkgDataInfoMap from mAppDataIsolationWhitelistedApps, so
+            // it won't be mounted twice.
+            final Set<String> whitelistedApps = new ArraySet<>(mAppDataIsolationWhitelistedApps);
+            for (String pkg : targetPackagesList) {
+                whitelistedApps.remove(pkg);
+            }
+
+            whitelistedAppDataInfoMap = getPackageAppDataInfoMap(pmInt,
+                    whitelistedApps.toArray(new String[0]), uid);
+            if (whitelistedAppDataInfoMap == null) {
+                // TODO(b/152760674): Handle inode == 0 case properly, now we just give it a
+                // tmp free pass.
+                bindMountAppsData = false;
+            }
+
+            int userId = UserHandle.getUserId(uid);
             StorageManagerInternal storageManagerInternal = LocalServices.getService(
                     StorageManagerInternal.class);
-            final Map<String, Pair<String, Long>> pkgDataInfoMap;
-            boolean bindMountAppStorageDirs = false;
-
-            if (mAppDataIsolationEnabled && UserHandle.isApp(app.uid)
-                    && mPlatformCompat.isChangeEnabled(APP_DATA_DIRECTORY_ISOLATION, app.info)) {
-                // Get all packages belongs to the same shared uid. sharedPackages is empty array
-                // if it doesn't have shared uid.
-                final PackageManagerInternal pmInt = mService.getPackageManagerInternalLocked();
-                final String[] sharedPackages = pmInt.getSharedUserPackagesForPackage(
-                        app.info.packageName, app.userId);
-                pkgDataInfoMap = getPackageAppDataInfoMap(pmInt, sharedPackages.length == 0
-                        ? new String[]{app.info.packageName} : sharedPackages, uid);
-
-                int userId = UserHandle.getUserId(uid);
-                if (mVoldAppDataIsolationEnabled
-                        && !storageManagerInternal.isExternalStorageService(uid)) {
-                    bindMountAppStorageDirs = true;
-                    if (!storageManagerInternal.prepareStorageDirs(userId, pkgDataInfoMap.keySet(),
-                            app.processName)) {
-                        // Cannot prepare Android/app and Android/obb directory,
-                        // so we won't mount it in zygote.
-                        app.bindMountPending = true;
-                        bindMountAppStorageDirs = false;
-                    }
+            if (mVoldAppDataIsolationEnabled && UserHandle.isApp(app.uid)
+                    && !storageManagerInternal.isExternalStorageService(uid)) {
+                bindMountAppStorageDirs = true;
+                if (!storageManagerInternal.prepareStorageDirs(userId, pkgDataInfoMap.keySet(),
+                        app.processName)) {
+                    // Cannot prepare Android/app and Android/obb directory,
+                    // so we won't mount it in zygote.
+                    app.bindMountPending = true;
+                    bindMountAppStorageDirs = false;
                 }
-            } else {
-                pkgDataInfoMap = null;
             }
 
             final Process.ProcessStartResult startResult;
@@ -2206,7 +2224,8 @@
                         app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
                         app.info.dataDir, null, app.info.packageName,
                         /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp,
-                        app.mDisabledCompatChanges, pkgDataInfoMap, bindMountAppStorageDirs,
+                        app.mDisabledCompatChanges, pkgDataInfoMap, whitelistedAppDataInfoMap,
+                        bindMountAppsData, bindMountAppStorageDirs,
                         new String[]{PROC_START_SEQ_IDENT + app.startSeq});
             } else {
                 startResult = Process.start(entryPoint,
@@ -2214,7 +2233,7 @@
                         app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
                         app.info.dataDir, invokeWith, app.info.packageName, zygotePolicyFlags,
                         isTopApp, app.mDisabledCompatChanges, pkgDataInfoMap,
-                        bindMountAppStorageDirs,
+                        whitelistedAppDataInfoMap, bindMountAppsData, bindMountAppStorageDirs,
                         new String[]{PROC_START_SEQ_IDENT + app.startSeq});
             }
             checkSlow(startTime, "startProcess: returned from zygote!");
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index 4431abe..808f8c21 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -558,8 +558,10 @@
             FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
                     statsModality(), BiometricsProtoEnums.ISSUE_CANCEL_TIMED_OUT);
 
+            ClientMonitor newClient = mPendingClient;
             mCurrentClient = null;
-            startClient(mPendingClient, false);
+            mPendingClient = null;
+            startClient(newClient, false);
         }
     }
 
diff --git a/services/core/java/com/android/server/display/color/AppSaturationController.java b/services/core/java/com/android/server/display/color/AppSaturationController.java
index e42be02..6a685bf 100644
--- a/services/core/java/com/android/server/display/color/AppSaturationController.java
+++ b/services/core/java/com/android/server/display/color/AppSaturationController.java
@@ -17,6 +17,7 @@
 package com.android.server.display.color;
 
 import android.annotation.UserIdInt;
+import android.util.ArrayMap;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
@@ -61,11 +62,12 @@
      * Set the saturation level ({@code ColorDisplayManager#SaturationLevel} constant for a given
      * package name and userId.
      */
-    public boolean setSaturationLevel(String packageName, @UserIdInt int userId,
+    public boolean setSaturationLevel(String callingPackageName, String affectedPackageName,
+            @UserIdInt int userId,
             int saturationLevel) {
         synchronized (mLock) {
-            return getSaturationControllerLocked(packageName, userId)
-                    .setSaturationLevel(saturationLevel);
+            return getSaturationControllerLocked(affectedPackageName, userId)
+                    .setSaturationLevel(callingPackageName, saturationLevel);
         }
     }
 
@@ -148,13 +150,19 @@
 
     private static class SaturationController {
 
+        private static final int FULL_SATURATION = 100;
+
         private final List<WeakReference<ColorTransformController>> mControllerRefs =
                 new ArrayList<>();
-        private int mSaturationLevel = 100;
+        private final ArrayMap<String, Integer> mSaturationLevels = new ArrayMap<>();
         private float[] mTransformMatrix = new float[9];
 
-        private boolean setSaturationLevel(int saturationLevel) {
-            mSaturationLevel = saturationLevel;
+        private boolean setSaturationLevel(String callingPackageName, int saturationLevel) {
+            if (saturationLevel == FULL_SATURATION) {
+                mSaturationLevels.remove(callingPackageName);
+            } else {
+                mSaturationLevels.put(callingPackageName, saturationLevel);
+            }
             if (!mControllerRefs.isEmpty()) {
                 return updateState();
             }
@@ -163,17 +171,27 @@
 
         private boolean addColorTransformController(
                 WeakReference<ColorTransformController> controller) {
+            clearExpiredReferences();
             mControllerRefs.add(controller);
-            if (mSaturationLevel != 100) {
+            if (!mSaturationLevels.isEmpty()) {
                 return updateState();
-            } else {
-                clearExpiredReferences();
             }
             return false;
         }
 
+        private int calculateSaturationLevel() {
+            int saturationLevel = FULL_SATURATION;
+            for (int i = 0; i < mSaturationLevels.size(); i++) {
+                final int level = mSaturationLevels.valueAt(i);
+                if (level < saturationLevel) {
+                    saturationLevel = level;
+                }
+            }
+            return saturationLevel;
+        }
+
         private boolean updateState() {
-            computeGrayscaleTransformMatrix(mSaturationLevel / 100f, mTransformMatrix);
+            computeGrayscaleTransformMatrix(calculateSaturationLevel() / 100f, mTransformMatrix);
 
             boolean updated = false;
             final Iterator<WeakReference<ColorTransformController>> iterator = mControllerRefs
@@ -190,7 +208,6 @@
                 }
             }
             return updated;
-
         }
 
         private void clearExpiredReferences() {
@@ -206,7 +223,7 @@
         }
 
         private void dump(PrintWriter pw) {
-            pw.println("            mSaturationLevel: " + mSaturationLevel);
+            pw.println("            mSaturationLevels: " + mSaturationLevels);
             pw.println("            mControllerRefs count: " + mControllerRefs.size());
         }
     }
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 2dc2cf0..95a98f1 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -44,6 +44,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.display.ColorDisplayManager;
@@ -73,6 +74,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
 import com.android.server.DisplayThread;
+import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.twilight.TwilightListener;
 import com.android.server.twilight.TwilightManager;
@@ -817,9 +819,11 @@
         return LocalDateTime.MIN;
     }
 
-    private boolean setAppSaturationLevelInternal(String packageName, int saturationLevel) {
+    private boolean setAppSaturationLevelInternal(String callingPackageName,
+            String affectedPackageName, int saturationLevel) {
         return mAppSaturationController
-                .setSaturationLevel(packageName, mCurrentUser, saturationLevel);
+                .setSaturationLevel(callingPackageName, affectedPackageName, mCurrentUser,
+                        saturationLevel);
     }
 
     private void setColorModeInternal(@ColorMode int colorMode) {
@@ -1533,9 +1537,11 @@
             getContext().enforceCallingPermission(
                     Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
                     "Permission required to set display saturation level");
+            final String callingPackageName = LocalServices.getService(PackageManagerInternal.class)
+                    .getNameForUid(Binder.getCallingUid());
             final long token = Binder.clearCallingIdentity();
             try {
-                return setAppSaturationLevelInternal(packageName, level);
+                return setAppSaturationLevelInternal(callingPackageName, packageName, level);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 846d099..e6cb371 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3106,14 +3106,10 @@
                     MSG_SHOW_SOFT_INPUT, getImeShowFlags(), reason, mCurMethod, resultReceiver,
                     showInputToken));
             mInputShown = true;
-
             if (mHaveConnection && !mVisibleBound) {
                 bindCurrentInputMethodServiceLocked(
                         mCurIntent, mVisibleConnection, IME_VISIBLE_BIND_FLAGS);
                 mVisibleBound = true;
-            } else {
-                // Clear the show request after the input shown.
-                mShowRequested = false;
             }
             res = true;
         } else if (mHaveConnection && SystemClock.uptimeMillis()
diff --git a/services/core/java/com/android/server/location/AppOpsHelper.java b/services/core/java/com/android/server/location/AppOpsHelper.java
index 9c27916..cb64c50 100644
--- a/services/core/java/com/android/server/location/AppOpsHelper.java
+++ b/services/core/java/com/android/server/location/AppOpsHelper.java
@@ -191,7 +191,7 @@
                     callerIdentity.uid,
                     callerIdentity.packageName,
                     callerIdentity.featureId,
-                    null) == AppOpsManager.MODE_ALLOWED;
+                    callerIdentity.listenerId) == AppOpsManager.MODE_ALLOWED;
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -210,7 +210,7 @@
                     callerIdentity.packageName,
                     false,
                     callerIdentity.featureId,
-                    null) == AppOpsManager.MODE_ALLOWED;
+                    callerIdentity.listenerId) == AppOpsManager.MODE_ALLOWED;
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -245,7 +245,7 @@
                     callerIdentity.uid,
                     callerIdentity.packageName,
                     callerIdentity.featureId,
-                    null) == AppOpsManager.MODE_ALLOWED;
+                    callerIdentity.listenerId) == AppOpsManager.MODE_ALLOWED;
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/services/core/java/com/android/server/location/CallerIdentity.java b/services/core/java/com/android/server/location/CallerIdentity.java
index b84fd13..8d508bb 100644
--- a/services/core/java/com/android/server/location/CallerIdentity.java
+++ b/services/core/java/com/android/server/location/CallerIdentity.java
@@ -83,12 +83,22 @@
      */
     public static CallerIdentity fromBinder(Context context, String packageName,
             @Nullable String featureId) {
+        return fromBinder(context, packageName, featureId, null);
+    }
+
+    /**
+     * Creates a CallerIdentity from the current binder identity, using the given package, feature
+     * id, and listener id. The package will be checked to enforce it belongs to the calling uid,
+     * and a security exception will be thrown if it is invalid.
+     */
+    public static CallerIdentity fromBinder(Context context, String packageName,
+            @Nullable String featureId, @Nullable String listenerId) {
         int uid = Binder.getCallingUid();
         if (!ArrayUtils.contains(context.getPackageManager().getPackagesForUid(uid), packageName)) {
             throw new SecurityException("invalid package \"" + packageName + "\" for uid " + uid);
         }
 
-        return fromBinderUnsafe(context, packageName, featureId);
+        return fromBinderUnsafe(context, packageName, featureId, listenerId);
     }
 
     /**
@@ -99,8 +109,19 @@
      */
     public static CallerIdentity fromBinderUnsafe(Context context, String packageName,
             @Nullable String featureId) {
+        return fromBinderUnsafe(context, packageName, featureId, null);
+    }
+
+    /**
+     * Creates a CallerIdentity from the current binder identity, using the given package, feature
+     * id, and listener id. The package will not be checked to enforce that it belongs to the
+     * calling uid - this method should only be used if the package will be validated by some other
+     * means, such as an appops call.
+     */
+    public static CallerIdentity fromBinderUnsafe(Context context, String packageName,
+            @Nullable String featureId, @Nullable String listenerId) {
         return new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(),
-                UserHandle.getCallingUserId(), packageName, featureId,
+                UserHandle.getCallingUserId(), packageName, featureId, listenerId,
                 getBinderPermissionLevel(context));
     }
 
@@ -157,6 +178,9 @@
     /** The calling feature id. */
     public final @Nullable String featureId;
 
+    /** The calling listener id. */
+    public final @Nullable String listenerId;
+
     /**
      * The calling location permission level. This field should only be used for validating
      * permissions for API access. It should not be used for validating permissions for location
@@ -167,11 +191,18 @@
     @VisibleForTesting
     public CallerIdentity(int uid, int pid, int userId, String packageName,
             @Nullable String featureId, @PermissionLevel int permissionLevel) {
+        this(uid, pid, userId, packageName, featureId, null, permissionLevel);
+    }
+
+    private CallerIdentity(int uid, int pid, int userId, String packageName,
+            @Nullable String featureId, @Nullable String listenerId,
+            @PermissionLevel int permissionLevel) {
         this.uid = uid;
         this.pid = pid;
         this.userId = userId;
         this.packageName = Objects.requireNonNull(packageName);
         this.featureId = featureId;
+        this.listenerId = listenerId;
         this.permissionLevel = Preconditions.checkArgumentInRange(permissionLevel, PERMISSION_NONE,
                 PERMISSION_FINE, "permissionLevel");
     }
@@ -216,7 +247,8 @@
         return uid == that.uid
                 && pid == that.pid
                 && packageName.equals(that.packageName)
-                && Objects.equals(featureId, that.featureId);
+                && Objects.equals(featureId, that.featureId)
+                && Objects.equals(listenerId, that.listenerId);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 1931079..c3413e8 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -98,18 +98,26 @@
     public List<MediaRoute2Info> getSystemRoutes() {
         final int uid = Binder.getCallingUid();
         final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
+        final boolean hasModifyAudioRoutingPermission = mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+                == PackageManager.PERMISSION_GRANTED;
 
         final long token = Binder.clearCallingIdentity();
         try {
             Collection<MediaRoute2Info> systemRoutes;
             synchronized (mLock) {
                 UserRecord userRecord = getOrCreateUserRecordLocked(userId);
-                MediaRoute2ProviderInfo providerInfo =
-                        userRecord.mHandler.mSystemProvider.getProviderInfo();
-                if (providerInfo != null) {
-                    systemRoutes = providerInfo.getRoutes();
+                if (hasModifyAudioRoutingPermission) {
+                    MediaRoute2ProviderInfo providerInfo =
+                            userRecord.mHandler.mSystemProvider.getProviderInfo();
+                    if (providerInfo != null) {
+                        systemRoutes = providerInfo.getRoutes();
+                    } else {
+                        systemRoutes = Collections.emptyList();
+                    }
                 } else {
-                    systemRoutes = Collections.emptyList();
+                    systemRoutes = new ArrayList<>();
+                    systemRoutes.add(userRecord.mHandler.mSystemProvider.getDefaultRoute());
                 }
             }
             return new ArrayList<>(systemRoutes);
@@ -122,18 +130,25 @@
     public RoutingSessionInfo getSystemSessionInfo() {
         final int uid = Binder.getCallingUid();
         final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
+        final boolean hasModifyAudioRoutingPermission = mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+                == PackageManager.PERMISSION_GRANTED;
 
         final long token = Binder.clearCallingIdentity();
         try {
             RoutingSessionInfo systemSessionInfo = null;
             synchronized (mLock) {
                 UserRecord userRecord = getOrCreateUserRecordLocked(userId);
-                List<RoutingSessionInfo> sessionInfos =
-                        userRecord.mHandler.mSystemProvider.getSessionInfos();
-                if (sessionInfos != null && !sessionInfos.isEmpty()) {
-                    systemSessionInfo = sessionInfos.get(0);
+                List<RoutingSessionInfo> sessionInfos;
+                if (hasModifyAudioRoutingPermission) {
+                    sessionInfos = userRecord.mHandler.mSystemProvider.getSessionInfos();
+                    if (sessionInfos != null && !sessionInfos.isEmpty()) {
+                        systemSessionInfo = sessionInfos.get(0);
+                    } else {
+                        Slog.w(TAG, "System provider does not have any session info.");
+                    }
                 } else {
-                    Slog.w(TAG, "System provider does not have any session info.");
+                    systemSessionInfo = userRecord.mHandler.mSystemProvider.getDefaultSessionInfo();
                 }
             }
             return systemSessionInfo;
@@ -654,10 +669,20 @@
             return;
         }
 
-        routerRecord.mUserRecord.mHandler.sendMessage(
-                obtainMessage(UserHandler::transferToRouteOnHandler,
-                        routerRecord.mUserRecord.mHandler,
-                        DUMMY_REQUEST_ID, routerRecord, uniqueSessionId, route));
+        String defaultRouteId =
+                routerRecord.mUserRecord.mHandler.mSystemProvider.getDefaultRoute().getId();
+        if (route.isSystemRoute() && !routerRecord.mHasModifyAudioRoutingPermission
+                && !TextUtils.equals(route.getId(), defaultRouteId)) {
+            routerRecord.mUserRecord.mHandler.sendMessage(
+                    obtainMessage(UserHandler::notifySessionCreationFailedToRouter,
+                            routerRecord.mUserRecord.mHandler,
+                            routerRecord, toOriginalRequestId(DUMMY_REQUEST_ID)));
+        } else {
+            routerRecord.mUserRecord.mHandler.sendMessage(
+                    obtainMessage(UserHandler::transferToRouteOnHandler,
+                            routerRecord.mUserRecord.mHandler,
+                            DUMMY_REQUEST_ID, routerRecord, uniqueSessionId, route));
+        }
     }
 
     private void setSessionVolumeWithRouter2Locked(@NonNull IMediaRouter2 router,
@@ -1185,18 +1210,42 @@
                 }
             }
 
-            List<IMediaRouter2> routers = getRouters();
+            List<IMediaRouter2> routersWithModifyAudioRoutingPermission = getRouters(true);
+            List<IMediaRouter2> routersWithoutModifyAudioRoutingPermission = getRouters(false);
             List<IMediaRouter2Manager> managers = getManagers();
+            List<MediaRoute2Info> defaultRoute = new ArrayList<>();
+            defaultRoute.add(mSystemProvider.getDefaultRoute());
+
             if (addedRoutes.size() > 0) {
-                notifyRoutesAddedToRouters(routers, addedRoutes);
+                notifyRoutesAddedToRouters(routersWithModifyAudioRoutingPermission, addedRoutes);
+                if (!provider.mIsSystemRouteProvider) {
+                    notifyRoutesAddedToRouters(routersWithoutModifyAudioRoutingPermission,
+                            addedRoutes);
+                } else if (prevInfo == null) {
+                    notifyRoutesAddedToRouters(routersWithoutModifyAudioRoutingPermission,
+                            defaultRoute);
+                } // 'else' is handled as changed routes
                 notifyRoutesAddedToManagers(managers, addedRoutes);
             }
             if (removedRoutes.size() > 0) {
-                notifyRoutesRemovedToRouters(routers, removedRoutes);
+                notifyRoutesRemovedToRouters(routersWithModifyAudioRoutingPermission,
+                        removedRoutes);
+                if (!provider.mIsSystemRouteProvider) {
+                    notifyRoutesRemovedToRouters(routersWithoutModifyAudioRoutingPermission,
+                            removedRoutes);
+                }
                 notifyRoutesRemovedToManagers(managers, removedRoutes);
             }
             if (changedRoutes.size() > 0) {
-                notifyRoutesChangedToRouters(routers, changedRoutes);
+                notifyRoutesChangedToRouters(routersWithModifyAudioRoutingPermission,
+                        changedRoutes);
+                if (!provider.mIsSystemRouteProvider) {
+                    notifyRoutesChangedToRouters(routersWithoutModifyAudioRoutingPermission,
+                            changedRoutes);
+                } else if (prevInfo != null) {
+                    notifyRoutesChangedToRouters(routersWithoutModifyAudioRoutingPermission,
+                            defaultRoute);
+                } // 'else' is handled as added routes
                 notifyRoutesChangedToManagers(managers, changedRoutes);
             }
         }
@@ -1223,6 +1272,15 @@
                         toOriginalRequestId(uniqueRequestId));
                 return;
             }
+            if (route.isSystemRoute() && !routerRecord.mHasModifyAudioRoutingPermission
+                    && !TextUtils.equals(route.getId(),
+                            mSystemProvider.getDefaultRoute().getId())) {
+                Slog.w(TAG, "MODIFY_AUDIO_ROUTING permission is required to transfer to"
+                        + route);
+                notifySessionCreationFailedToRouter(routerRecord,
+                        toOriginalRequestId(uniqueRequestId));
+                return;
+            }
 
             SessionCreationRequest request =
                     new SessionCreationRequest(routerRecord, uniqueRequestId, route, managerRecord);
@@ -1444,7 +1502,9 @@
                 if (service == null) {
                     return;
                 }
-                notifySessionInfoChangedToRouters(getRouters(), sessionInfo);
+                notifySessionInfoChangedToRouters(getRouters(true), sessionInfo);
+                notifySessionInfoChangedToRouters(getRouters(false),
+                        mSystemProvider.getDefaultSessionInfo());
                 return;
             }
 
@@ -1565,7 +1625,7 @@
             }
         }
 
-        private List<IMediaRouter2> getRouters() {
+        private List<IMediaRouter2> getAllRouters() {
             final List<IMediaRouter2> routers = new ArrayList<>();
             MediaRouter2ServiceImpl service = mServiceRef.get();
             if (service == null) {
@@ -1579,6 +1639,23 @@
             return routers;
         }
 
+        private List<IMediaRouter2> getRouters(boolean hasModifyAudioRoutingPermission) {
+            final List<IMediaRouter2> routers = new ArrayList<>();
+            MediaRouter2ServiceImpl service = mServiceRef.get();
+            if (service == null) {
+                return routers;
+            }
+            synchronized (service.mLock) {
+                for (RouterRecord routerRecord : mUserRecord.mRouterRecords) {
+                    if (hasModifyAudioRoutingPermission
+                            == routerRecord.mHasModifyAudioRoutingPermission) {
+                        routers.add(routerRecord.mRouter);
+                    }
+                }
+            }
+            return routers;
+        }
+
         private List<IMediaRouter2Manager> getManagers() {
             final List<IMediaRouter2Manager> managers = new ArrayList<>();
             MediaRouter2ServiceImpl service = mServiceRef.get();
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 5b16d68..6e2feeb 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -72,6 +72,7 @@
     // This should be the currently selected route.
     MediaRoute2Info mDefaultRoute;
     MediaRoute2Info mDeviceRoute;
+    RoutingSessionInfo mDefaultSessionInfo;
     final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
 
     final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
@@ -114,6 +115,7 @@
             }
         });
         updateSessionInfosIfNeeded();
+
         mContext.registerReceiver(new VolumeChangeReceiver(),
                 new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));
 
@@ -156,6 +158,10 @@
 
     @Override
     public void transferToRoute(long requestId, String sessionId, String routeId) {
+        if (TextUtils.equals(routeId, DEFAULT_ROUTE_ID)) {
+            // The currently selected route is the default route.
+            return;
+        }
         if (mBtRouteProvider != null) {
             if (TextUtils.equals(routeId, mDeviceRoute.getId())) {
                 mBtRouteProvider.transferTo(null);
@@ -182,6 +188,10 @@
         return mDefaultRoute;
     }
 
+    public RoutingSessionInfo getDefaultSessionInfo() {
+        return mDefaultSessionInfo;
+    }
+
     private void updateDeviceRoute(AudioRoutesInfo newRoutes) {
         int name = R.string.default_audio_route_name;
         if (newRoutes != null) {
@@ -229,8 +239,6 @@
      */
     boolean updateSessionInfosIfNeeded() {
         synchronized (mLock) {
-            // Prevent to execute this method before mBtRouteProvider is created.
-            if (mBtRouteProvider == null) return false;
             RoutingSessionInfo oldSessionInfo = mSessionInfos.isEmpty() ? null : mSessionInfos.get(
                     0);
 
@@ -238,14 +246,19 @@
                     SYSTEM_SESSION_ID, "" /* clientPackageName */)
                     .setSystemSession(true);
 
-            MediaRoute2Info selectedRoute = mBtRouteProvider.getSelectedRoute();
-            if (selectedRoute == null) {
-                selectedRoute = mDeviceRoute;
-            } else {
-                builder.addTransferableRoute(mDeviceRoute.getId());
+            MediaRoute2Info selectedRoute = mDeviceRoute;
+            if (mBtRouteProvider != null) {
+                MediaRoute2Info selectedBtRoute = mBtRouteProvider.getSelectedRoute();
+                if (selectedBtRoute != null) {
+                    selectedRoute = selectedBtRoute;
+                    builder.addTransferableRoute(mDeviceRoute.getId());
+                }
             }
             mSelectedRouteId = selectedRoute.getId();
-            mDefaultRoute = new MediaRoute2Info.Builder(DEFAULT_ROUTE_ID, selectedRoute).build();
+            mDefaultRoute = new MediaRoute2Info.Builder(DEFAULT_ROUTE_ID, selectedRoute)
+                    .setSystemRoute(true)
+                    .setProviderId(mUniqueId)
+                    .build();
             builder.addSelectedRoute(mSelectedRouteId);
 
             for (MediaRoute2Info route : mBtRouteProvider.getTransferableRoutes()) {
@@ -258,6 +271,12 @@
             } else {
                 mSessionInfos.clear();
                 mSessionInfos.add(newSessionInfo);
+                mDefaultSessionInfo = new RoutingSessionInfo.Builder(
+                        SYSTEM_SESSION_ID, "" /* clientPackageName */)
+                        .setProviderId(mUniqueId)
+                        .setSystemSession(true)
+                        .addSelectedRoute(DEFAULT_ROUTE_ID)
+                        .build();
                 return true;
             }
         }
@@ -302,6 +321,9 @@
                 } else if (mBtRouteProvider != null) {
                     mBtRouteProvider.setSelectedRouteVolume(newVolume);
                 }
+                mDefaultRoute = new MediaRoute2Info.Builder(mDefaultRoute)
+                        .setVolume(newVolume)
+                        .build();
                 publishProviderState();
             }
         }
diff --git a/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java b/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
new file mode 100644
index 0000000..0bdf3f2
--- /dev/null
+++ b/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2020 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.net;
+
+import static android.net.NetworkTemplate.getCollapsedRatType;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.telephony.Annotation;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+
+/**
+ * Helper class that watches for events that are triggered per subscription.
+ */
+// TODO (b/152176562): Write tests to verify subscription changes generate corresponding
+//  register/unregister calls.
+public class NetworkStatsSubscriptionsMonitor extends
+        SubscriptionManager.OnSubscriptionsChangedListener {
+
+    /**
+     * Interface that this monitor uses to delegate event handling to NetworkStatsService.
+     */
+    public interface Delegate {
+        /**
+         * Notify that the collapsed RAT type has been changed for any subscription. The method
+         * will also be triggered for any existing sub when start and stop monitoring.
+         *
+         * @param subscriberId IMSI of the subscription.
+         * @param collapsedRatType collapsed RAT type.
+         *                         @see android.net.NetworkTemplate#getCollapsedRatType(int).
+         */
+        void onCollapsedRatTypeChanged(@NonNull String subscriberId,
+                @Annotation.NetworkType int collapsedRatType);
+    }
+    private final Delegate mDelegate;
+
+    /**
+     * Receivers that watches for {@link ServiceState} changes for each subscription, to
+     * monitor the transitioning between Radio Access Technology(RAT) types for each sub.
+     */
+    @NonNull
+    private final CopyOnWriteArrayList<RatTypeListener> mRatListeners =
+            new CopyOnWriteArrayList<>();
+
+    @NonNull
+    private final SubscriptionManager mSubscriptionManager;
+    @NonNull
+    private final TelephonyManager mTeleManager;
+
+    @NonNull
+    private final Executor mExecutor;
+
+    NetworkStatsSubscriptionsMonitor(@NonNull Context context, @NonNull Executor executor,
+            @NonNull Delegate delegate) {
+        super();
+        mSubscriptionManager = (SubscriptionManager) context.getSystemService(
+                Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+        mTeleManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        mExecutor = executor;
+        mDelegate = delegate;
+    }
+
+    @Override
+    public void onSubscriptionsChanged() {
+        // Collect active subId list, hidden subId such as opportunistic subscriptions are
+        // also needed to track CBRS.
+        final List<Integer> newSubs = getActiveSubIdList(mSubscriptionManager);
+
+        for (final int subId : newSubs) {
+            final RatTypeListener match = CollectionUtils.find(mRatListeners,
+                    it -> it.mSubId == subId);
+            if (match != null) continue;
+
+            // Create listener for every newly added sub. Also store subscriberId into it to
+            // prevent binder call to telephony when querying RAT.
+            final String subscriberId = mTeleManager.getSubscriberId(subId);
+            if (TextUtils.isEmpty(subscriberId)) {
+                Log.wtf(NetworkStatsService.TAG,
+                        "Empty subscriberId for newly added sub: " + subId);
+            }
+            final RatTypeListener listener =
+                    new RatTypeListener(mExecutor, this, subId, subscriberId);
+            mRatListeners.add(listener);
+
+            // Register listener to the telephony manager that associated with specific sub.
+            mTeleManager.createForSubscriptionId(subId)
+                    .listen(listener, PhoneStateListener.LISTEN_SERVICE_STATE);
+        }
+
+        for (final RatTypeListener listener : new ArrayList<>(mRatListeners)) {
+            // If the new list contains the subId of the listener, keeps it.
+            final Integer match = CollectionUtils.find(newSubs, it -> it == listener.mSubId);
+            if (match != null) continue;
+
+            handleRemoveRatTypeListener(listener);
+        }
+    }
+
+    @NonNull
+    private List<Integer> getActiveSubIdList(@NonNull SubscriptionManager subscriptionManager) {
+        final ArrayList<Integer> ret = new ArrayList<>();
+        final int[] ids = subscriptionManager.getCompleteActiveSubscriptionIdList();
+        for (int id : ids) ret.add(id);
+        return ret;
+    }
+
+    /**
+     * Get a collapsed RatType for the given subscriberId.
+     *
+     * @param subscriberId the target subscriberId
+     * @return collapsed RatType for the given subscriberId
+     */
+    public int getRatTypeForSubscriberId(@NonNull String subscriberId) {
+        final RatTypeListener match = CollectionUtils.find(mRatListeners,
+                it -> TextUtils.equals(subscriberId, it.mSubscriberId));
+        return match != null ? match.mLastCollapsedRatType : TelephonyManager.NETWORK_TYPE_UNKNOWN;
+    }
+
+    /**
+     * Start monitoring events that triggered per subscription.
+     */
+    public void start() {
+        mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor, this);
+    }
+
+    /**
+     * Unregister subscription changes and all listeners for each subscription.
+     */
+    public void stop() {
+        mSubscriptionManager.removeOnSubscriptionsChangedListener(this);
+
+        for (final RatTypeListener listener : new ArrayList<>(mRatListeners)) {
+            handleRemoveRatTypeListener(listener);
+        }
+    }
+
+    private void handleRemoveRatTypeListener(@NonNull RatTypeListener listener) {
+        mTeleManager.createForSubscriptionId(listener.mSubId)
+                .listen(listener, PhoneStateListener.LISTEN_NONE);
+        mRatListeners.remove(listener);
+
+        // Removal of subscriptions doesn't generate RAT changed event, fire it for every
+        // RatTypeListener.
+        mDelegate.onCollapsedRatTypeChanged(
+                listener.mSubscriberId, TelephonyManager.NETWORK_TYPE_UNKNOWN);
+    }
+
+    static class RatTypeListener extends PhoneStateListener {
+        // Unique id for the subscription. See {@link SubscriptionInfo#getSubscriptionId}.
+        @NonNull
+        private final int mSubId;
+
+        // IMSI to identifying the corresponding network from {@link NetworkState}.
+        // See {@link TelephonyManager#getSubscriberId}.
+        @NonNull
+        private final String mSubscriberId;
+
+        private volatile int mLastCollapsedRatType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+        @NonNull
+        private final NetworkStatsSubscriptionsMonitor mMonitor;
+
+        RatTypeListener(@NonNull Executor executor,
+                @NonNull NetworkStatsSubscriptionsMonitor monitor, int subId,
+                @NonNull String subscriberId) {
+            super(executor);
+            mSubId = subId;
+            mSubscriberId = subscriberId;
+            mMonitor = monitor;
+        }
+
+        @Override
+        public void onServiceStateChanged(@NonNull ServiceState ss) {
+            final int networkType = ss.getDataNetworkType();
+            final int collapsedRatType = getCollapsedRatType(networkType);
+            if (collapsedRatType == mLastCollapsedRatType) return;
+
+            if (NetworkStatsService.LOGD) {
+                Log.d(NetworkStatsService.TAG, "subtype changed for sub(" + mSubId + "): "
+                        + mLastCollapsedRatType + " -> " + collapsedRatType);
+            }
+            mLastCollapsedRatType = collapsedRatType;
+            mMonitor.mDelegate.onCollapsedRatTypeChanged(mSubscriberId, mLastCollapsedRatType);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index ba7583f..dab4bfd 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -46,6 +46,7 @@
 
 import java.io.File;
 import java.nio.file.Paths;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
@@ -106,6 +107,8 @@
     private static final long mDowngradeUnusedAppsThresholdInMillis =
             getDowngradeUnusedAppsThresholdInMillis();
 
+    private static List<PackagesUpdatedListener> sPackagesUpdatedListeners = new ArrayList<>();
+
     public static void schedule(Context context) {
         if (isBackgroundDexoptDisabled()) {
             return;
@@ -244,6 +247,7 @@
             }
         }
         notifyPinService(updatedPackages);
+        notifyPackagesUpdated(updatedPackages);
         // Ran to completion, so we abandon our timeslice and do not reschedule.
         jobFinished(jobParams, /* reschedule */ false);
     }
@@ -391,6 +395,7 @@
         } finally {
             // Always let the pinner service know about changes.
             notifyPinService(updatedPackages);
+            notifyPackagesUpdated(updatedPackages);
         }
     }
 
@@ -642,6 +647,32 @@
         }
     }
 
+    public static interface PackagesUpdatedListener {
+        /** Callback when packages have been updated by the bg-dexopt service. */
+        public void onPackagesUpdated(ArraySet<String> updatedPackages);
+    }
+
+    public static void addPackagesUpdatedListener(PackagesUpdatedListener listener) {
+        synchronized (sPackagesUpdatedListeners) {
+            sPackagesUpdatedListeners.add(listener);
+        }
+    }
+
+    public static void removePackagesUpdatedListener(PackagesUpdatedListener listener) {
+        synchronized (sPackagesUpdatedListeners) {
+            sPackagesUpdatedListeners.remove(listener);
+        }
+    }
+
+    /** Notify all listeners (#addPackagesUpdatedListener) that packages have been updated. */
+    private void notifyPackagesUpdated(ArraySet<String> updatedPackages) {
+        synchronized (sPackagesUpdatedListeners) {
+            for (PackagesUpdatedListener listener : sPackagesUpdatedListeners) {
+                listener.onPackagesUpdated(updatedPackages);
+            }
+        }
+    }
+
     private static long getDowngradeUnusedAppsThresholdInMillis() {
         final String sysPropKey = "pm.dexopt.downgrade_after_inactive_days";
         String sysPropValue = SystemProperties.get(sysPropKey);
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index f37af3a..9fb468e 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -724,6 +724,30 @@
     }
 
     /**
+     * Deletes all snapshots of credential encrypted user data, where the snapshot id is not
+     * included in {@code retainSnapshotIds}.
+     *
+     * @param userId id of the user whose user data snapshots to delete.
+     * @param retainSnapshotIds ids of the snapshots that should not be deleted.
+     *
+     * @return {@code true} if the operation was successful, or {@code false} if a remote call
+     * shouldn't be continued. See {@link #checkBeforeRemote}.
+     *
+     * @throws InstallerException if failed to delete user data snapshot.
+     */
+    public boolean destroyCeSnapshotsNotSpecified(@UserIdInt int userId,
+            int[] retainSnapshotIds) throws InstallerException {
+        if (!checkBeforeRemote()) return false;
+
+        try {
+            mInstalld.destroyCeSnapshotsNotSpecified(null, userId, retainSnapshotIds);
+            return true;
+        } catch (Exception e) {
+            throw InstallerException.from(e);
+        }
+    }
+
+    /**
      * Migrates obb data from its legacy location {@code /data/media/obb} to
      * {@code /data/media/0/Android/obb}. This call is idempotent and a fast no-op if data has
      * already been migrated.
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 5f871ad4..83e99b0 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -505,6 +505,11 @@
             rollbackIds[i] = mRollbacks.get(i).info.getRollbackId();
         }
         ApexManager.getInstance().destroyCeSnapshotsNotSpecified(userId, rollbackIds);
+        try {
+            mInstaller.destroyCeSnapshotsNotSpecified(userId, rollbackIds);
+        } catch (Installer.InstallerException ie) {
+            Slog.e(TAG, "Failed to delete snapshots for user: " + userId, ie);
+        }
     }
 
     @WorkerThread
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 1e5a924..0470ffa 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -1469,7 +1469,7 @@
             if (toDisplay.getDisplayId() != stack.getDisplayId()) {
                 stack.reparent(toDisplay.getDefaultTaskDisplayArea(), false /* onTop */);
             } else {
-                toDisplay.mTaskContainers.positionStackAtBottom(stack);
+                toDisplay.getDefaultTaskDisplayArea().positionStackAtBottom(stack);
             }
 
             mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index edd14b7..a512337 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -54,7 +54,6 @@
  * - BELOW_TASKS: Can only contain BELOW_TASK DisplayAreas and WindowTokens that go below tasks.
  * - ABOVE_TASKS: Can only contain ABOVE_TASK DisplayAreas and WindowTokens that go above tasks.
  * - ANY: Can contain any kind of DisplayArea, and any kind of WindowToken or the Task container.
- *        Cannot have a sibling that is of type ANY.
  *
  * @param <T> type of the children of the DisplayArea.
  */
@@ -274,7 +273,6 @@
         ANY;
 
         static void checkSiblings(Type bottom, Type top) {
-            checkState(!(bottom == ANY && top == ANY), "ANY cannot be a sibling of ANY");
             checkState(!(bottom != BELOW_TASKS && top == BELOW_TASKS),
                     bottom + " must be above BELOW_TASKS");
             checkState(!(bottom == ABOVE_TASKS && top != ABOVE_TASKS),
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
index 9821573..15b6f973 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
@@ -16,9 +16,14 @@
 
 package com.android.server.wm;
 
+import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
+
 import android.content.res.Resources;
 import android.text.TextUtils;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Policy that manages DisplayAreas.
  */
@@ -37,9 +42,9 @@
     protected final DisplayArea<? extends WindowContainer> mImeContainer;
 
     /**
-     * The Tasks container. Tasks etc. are automatically added to this container.
+     * The task display areas. Tasks etc. are automatically added to these containers.
      */
-    protected final DisplayArea<? extends ActivityStack> mTaskContainers;
+    protected final List<TaskDisplayArea> mTaskDisplayAreas;
 
     /**
      * Construct a new {@link DisplayAreaPolicy}
@@ -48,19 +53,19 @@
      * @param content the display content for which the policy applies
      * @param root the root display area under which the policy operates
      * @param imeContainer the ime container that the policy must attach
-     * @param taskDisplayArea the task container that the policy must attach
+     * @param taskDisplayAreas the task display areas that the policy must attach
      *
      * @see #attachDisplayAreas()
      */
     protected DisplayAreaPolicy(WindowManagerService wmService,
             DisplayContent content, DisplayArea.Root root,
             DisplayArea<? extends WindowContainer> imeContainer,
-            DisplayArea<? extends ActivityStack> taskDisplayArea) {
+            List<TaskDisplayArea> taskDisplayAreas) {
         mWmService = wmService;
         mContent = content;
         mRoot = root;
         mImeContainer = imeContainer;
-        mTaskContainers = taskDisplayArea;
+        mTaskDisplayAreas = taskDisplayAreas;
     }
 
     /**
@@ -80,15 +85,32 @@
      */
     public abstract void addWindow(WindowToken token);
 
+    /**
+     * @return the number of task display areas on the display.
+     */
+    public int getTaskDisplayAreaCount() {
+        return mTaskDisplayAreas.size();
+    }
+
+    /**
+     * @return the task display area at index.
+     */
+    public TaskDisplayArea getTaskDisplayAreaAt(int index) {
+        return mTaskDisplayAreas.get(index);
+    }
+
     /** Provider for platform-default display area policy. */
     static final class DefaultProvider implements DisplayAreaPolicy.Provider {
         @Override
         public DisplayAreaPolicy instantiate(WindowManagerService wmService,
                 DisplayContent content, DisplayArea.Root root,
-                DisplayArea<? extends WindowContainer> imeContainer,
-                TaskDisplayArea taskDisplayArea) {
+                DisplayArea<? extends WindowContainer> imeContainer) {
+            final TaskDisplayArea defaultTaskDisplayArea = new TaskDisplayArea(content, wmService,
+                    "DefaultTaskDisplayArea", FEATURE_DEFAULT_TASK_CONTAINER);
+            final List<TaskDisplayArea> tdaList = new ArrayList<>();
+            tdaList.add(defaultTaskDisplayArea);
             return new DisplayAreaPolicyBuilder()
-                    .build(wmService, content, root, imeContainer, taskDisplayArea);
+                    .build(wmService, content, root, imeContainer, tdaList);
         }
     }
 
@@ -107,8 +129,7 @@
          */
         DisplayAreaPolicy instantiate(WindowManagerService wmService,
                 DisplayContent content, DisplayArea.Root root,
-                DisplayArea<? extends WindowContainer> imeContainer,
-                TaskDisplayArea taskDisplayArea);
+                DisplayArea<? extends WindowContainer> imeContainer);
 
         /**
          * Instantiate the device-specific {@link Provider}.
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
index e8becfa..2c2f433 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
@@ -201,8 +201,8 @@
 
         Result(WindowManagerService wmService, DisplayContent content, DisplayArea.Root root,
                 DisplayArea<? extends WindowContainer> imeContainer,
-                DisplayArea<? extends ActivityStack> taskDisplayArea, ArrayList<Feature> features) {
-            super(wmService, content, root, imeContainer, taskDisplayArea);
+                List<TaskDisplayArea> taskDisplayAreas, ArrayList<Feature> features) {
+            super(wmService, content, root, imeContainer, taskDisplayAreas);
             mFeatures = features;
             mAreas = new HashMap<>(features.size());
             for (int i = 0; i < mFeatures.size(); i++) {
@@ -267,7 +267,7 @@
                     areaForLayer[layer].mChildren.add(leafArea);
                     leafType = type;
                     if (leafType == LEAF_TYPE_TASK_CONTAINERS) {
-                        leafArea.mExisting = mTaskContainers;
+                        addTaskDisplayAreasToLayer(areaForLayer[layer], layer);
                     } else if (leafType == LEAF_TYPE_IME_CONTAINERS) {
                         leafArea.mExisting = mImeContainer;
                     }
@@ -278,6 +278,17 @@
             root.instantiateChildren(mRoot, mAreaForLayer, 0, mAreas);
         }
 
+        /** Adds all task display areas to the specified layer */
+        private void addTaskDisplayAreasToLayer(PendingArea parentPendingArea, int layer) {
+            final int count = mTaskDisplayAreas.size();
+            for (int i = 0; i < count; i++) {
+                PendingArea leafArea = new PendingArea(null, layer, parentPendingArea);
+                leafArea.mExisting = mTaskDisplayAreas.get(i);
+                leafArea.mMaxLayer = layer;
+                parentPendingArea.mChildren.add(leafArea);
+            }
+        }
+
         @Override
         public void addWindow(WindowToken token) {
             DisplayArea.Tokens area = findAreaForToken(token);
@@ -317,12 +328,16 @@
         return this;
     }
 
+    protected List<Feature> getFeatures() {
+        return mFeatures;
+    }
+
     Result build(WindowManagerService wmService,
             DisplayContent content, DisplayArea.Root root,
             DisplayArea<? extends WindowContainer> imeContainer,
-            DisplayArea<? extends ActivityStack> taskDisplayArea) {
+            List<TaskDisplayArea> taskDisplayAreas) {
 
-        return new Result(wmService, content, root, imeContainer, taskDisplayArea, new ArrayList<>(
+        return new Result(wmService, content, root, imeContainer, taskDisplayAreas, new ArrayList<>(
                 mFeatures));
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8111c0e..40243e8 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -72,6 +72,7 @@
 import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN;
 import static android.view.WindowManager.TRANSIT_TASK_OPEN;
 import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
+import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
 
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
@@ -268,8 +269,6 @@
             new NonAppWindowContainers("mOverlayContainers", mWmService);
 
     /** The containers below are the only child containers {@link #mWindowContainers} can have. */
-    // Contains all window containers that are related to apps (Activities)
-    final TaskDisplayArea mTaskContainers = new TaskDisplayArea(this, mWmService);
 
     // Contains all IME window containers. Note that the z-ordering of the IME windows will depend
     // on the IME target. We mainly have this container grouping so we can keep track of all the IME
@@ -971,7 +970,7 @@
         super.addChild(mOverlayContainers, null);
 
         mDisplayAreaPolicy = mWmService.mDisplayAreaPolicyProvider.instantiate(
-                mWmService, this, mRootDisplayArea, mImeWindowsContainers, mTaskContainers);
+                mWmService, this, mRootDisplayArea, mImeWindowsContainers);
         mWindowContainers.addChildren();
 
         // Sets the display content for the children.
@@ -1081,8 +1080,7 @@
             return null;
         }
         mShellRoots.put(windowType, root);
-        SurfaceControl out = new SurfaceControl();
-        out.copyFrom(rootLeash);
+        SurfaceControl out = new SurfaceControl(rootLeash);
         return out;
     }
 
@@ -2070,13 +2068,11 @@
     }
 
     protected int getTaskDisplayAreaCount() {
-        // TODO(multi-display-area): Report actual display area count
-        return 1;
+        return mDisplayAreaPolicy.getTaskDisplayAreaCount();
     }
 
     protected TaskDisplayArea getTaskDisplayAreaAt(int index) {
-        // TODO(multi-display-area): Report actual display area values
-        return mTaskContainers;
+        return mDisplayAreaPolicy.getTaskDisplayAreaAt(index);
     }
 
     ActivityStack getStack(int rootTaskId) {
@@ -2402,7 +2398,7 @@
      * or for cases when multi-instance is not supported yet (like Split-screen, PiP or Recents).
      */
     TaskDisplayArea getDefaultTaskDisplayArea() {
-        return mTaskContainers;
+        return mDisplayAreaPolicy.getTaskDisplayAreaAt(0);
     }
 
     @Override
@@ -3425,7 +3421,10 @@
 
     private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim) {
         // Always update control target. This is needed to handle rotation.
-        updateImeControlTarget(target);
+        // We cannot set target as the control target, because mInputMethodTarget can only help
+        // decide the z-order of IME, but cannot control IME. Only the IME target reported from
+        // updateInputMethodTargetWindow can control IME.
+        updateImeControlTarget(mInputMethodControlTarget);
         if (target == mInputMethodTarget && mInputMethodTargetWaitingAnim == targetWaitingAnim) {
             return;
         }
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index ec3c99b..11cfad2 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -31,7 +31,6 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.window.DisplayAreaOrganizer.FEATURE_TASK_CONTAINER;
 
 import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
 import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE;
@@ -141,8 +140,9 @@
      */
     private boolean mRemoved;
 
-    TaskDisplayArea(DisplayContent displayContent, WindowManagerService service) {
-        super(service, Type.ANY, "TaskContainers", FEATURE_TASK_CONTAINER);
+    TaskDisplayArea(DisplayContent displayContent, WindowManagerService service, String name,
+            int displayAreaFeature) {
+        super(service, Type.ANY, name, displayAreaFeature);
         mDisplayContent = displayContent;
         mRootWindowContainer = service.mRoot;
         mAtmService = service.mAtmService;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index f9955fa..f6e952c 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2510,9 +2510,7 @@
             // We need to copy the SurfaceControl instead of returning the original
             // because the Parcel FLAGS PARCELABLE_WRITE_RETURN_VALUE cause SurfaceControls
             // to release themselves.
-            SurfaceControl sc = new SurfaceControl();
-            sc.copyFrom(wc.getSurfaceControl());
-            return sc;
+            return new SurfaceControl(wc.getSurfaceControl());
         }
 
         WindowContainerToken toWindowContainerToken() {
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 0635ae1..18ae4b5 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -137,6 +137,7 @@
                                           bool* _aidl_return) {
         mId = mountId;
         mListener = listener;
+        mServiceConnector = control.service;
         *_aidl_return = true;
         return binder::Status::ok();
     }
@@ -166,9 +167,17 @@
         mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_CREATED);
     }
 
+    int32_t setStorageParams(bool enableReadLogs) {
+        int32_t result = -1;
+        EXPECT_NE(mServiceConnector.get(), nullptr);
+        EXPECT_TRUE(mServiceConnector->setStorageParams(enableReadLogs, &result).isOk());
+        return result;
+    }
+
 private:
     int mId;
     sp<IDataLoaderStatusListener> mListener;
+    sp<IIncrementalServiceConnector> mServiceConnector;
     sp<IDataLoader> mDataLoader = sp<IDataLoader>(new FakeDataLoader());
 };
 
@@ -453,7 +462,7 @@
             mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
                                                IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
-    ASSERT_GE(mIncrementalService->setStorageParams(storageId, true), 0);
+    ASSERT_GE(mDataLoaderManager->setStorageParams(true), 0);
 }
 
 TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccessAndPermissionChanged) {
@@ -480,7 +489,7 @@
             mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
                                                IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
-    ASSERT_GE(mIncrementalService->setStorageParams(storageId, true), 0);
+    ASSERT_GE(mDataLoaderManager->setStorageParams(true), 0);
     ASSERT_NE(nullptr, mAppOpsManager->mStoredCallback.get());
     mAppOpsManager->mStoredCallback->opChanged(0, {});
 }
@@ -503,7 +512,7 @@
             mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
                                                IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
-    ASSERT_LT(mIncrementalService->setStorageParams(storageId, true), 0);
+    ASSERT_LT(mDataLoaderManager->setStorageParams(true), 0);
 }
 
 TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsFails) {
@@ -526,7 +535,7 @@
             mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
                                                IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
-    ASSERT_LT(mIncrementalService->setStorageParams(storageId, true), 0);
+    ASSERT_LT(mDataLoaderManager->setStorageParams(true), 0);
 }
 
 TEST_F(IncrementalServiceTest, testMakeDirectory) {
diff --git a/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java b/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java
index c225d3f..1aab672 100644
--- a/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java
+++ b/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.systemcaptions;
 
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
@@ -37,6 +39,8 @@
     private static final String SERVICE_INTERFACE =
             "android.service.systemcaptions.SystemCaptionsManagerService";
 
+    private static final int MSG_BIND = 1;
+
     private final Object mLock = new Object();
 
     private final Context mContext;
@@ -71,18 +75,26 @@
         if (mVerbose) {
             Slog.v(TAG, "initialize()");
         }
-        ensureBound();
+        scheduleBind();
     }
 
-    void destroy() {
+    /**
+     * Destroys this service.
+     */
+    public void destroy() {
+        mHandler.sendMessage(
+                obtainMessage(RemoteSystemCaptionsManagerService::handleDestroy, this));
+    }
+
+    void handleDestroy() {
         if (mVerbose) {
-            Slog.v(TAG, "destroy()");
+            Slog.v(TAG, "handleDestroy()");
         }
 
         synchronized (mLock) {
             if (mDestroyed) {
                 if (mVerbose) {
-                    Slog.v(TAG, "destroy(): Already destroyed");
+                    Slog.v(TAG, "handleDestroy(): Already destroyed");
                 }
                 return;
             }
@@ -97,14 +109,24 @@
         }
     }
 
-    private void ensureBound() {
+    private void scheduleBind() {
+        if (mHandler.hasMessages(MSG_BIND)) {
+            if (mVerbose) Slog.v(TAG, "scheduleBind(): already scheduled");
+            return;
+        }
+        mHandler.sendMessage(
+                obtainMessage(RemoteSystemCaptionsManagerService::handleEnsureBound, this)
+                .setWhat(MSG_BIND));
+    }
+
+    private void handleEnsureBound() {
         synchronized (mLock) {
             if (mService != null || mBinding) {
                 return;
             }
 
             if (mVerbose) {
-                Slog.v(TAG, "ensureBound(): binding");
+                Slog.v(TAG, "handleEnsureBound(): binding");
             }
             mBinding = true;
 
diff --git a/services/tests/servicestests/src/com/android/server/display/color/AppSaturationControllerTest.java b/services/tests/servicestests/src/com/android/server/display/color/AppSaturationControllerTest.java
index 7c9a81d..a525814 100644
--- a/services/tests/servicestests/src/com/android/server/display/color/AppSaturationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/color/AppSaturationControllerTest.java
@@ -43,7 +43,9 @@
 @RunWith(AndroidJUnit4.class)
 public class AppSaturationControllerTest {
 
-    private static final String TEST_PACKAGE_NAME = "com.android.test";
+    private static final String TEST_CALLER_PACKAGE_NAME = "com.android.test.caller";
+    private static final String TEST_CALLER_PACKAGE_NAME_TWO = "com.android.test.caller.two";
+    private static final String TEST_AFFECTED_PACKAGE_NAME = "com.android.test.affected";
 
     private int mUserId;
     private AppSaturationController mAppSaturationController;
@@ -70,8 +72,11 @@
     public void addColorTransformController_appliesExistingSaturation() {
         final WeakReference<ColorTransformController> ref = new WeakReference<>(
                 mColorTransformController);
-        mAppSaturationController.setSaturationLevel(TEST_PACKAGE_NAME, mUserId, 30);
-        mAppSaturationController.addColorTransformController(TEST_PACKAGE_NAME, mUserId, ref);
+        mAppSaturationController
+                .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId,
+                        30);
+        mAppSaturationController
+                .addColorTransformController(TEST_AFFECTED_PACKAGE_NAME, mUserId, ref);
         AppSaturationController.computeGrayscaleTransformMatrix(.3f, mMatrix);
         verify(mColorTransformController).applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
     }
@@ -80,14 +85,19 @@
     public void setSaturationLevel_resetToDefault() {
         final WeakReference<ColorTransformController> ref = new WeakReference<>(
                 mColorTransformController);
-        mAppSaturationController.addColorTransformController(TEST_PACKAGE_NAME, mUserId, ref);
+        mAppSaturationController
+                .addColorTransformController(TEST_AFFECTED_PACKAGE_NAME, mUserId, ref);
         verify(mColorTransformController, never())
                 .applyAppSaturation(any(), eq(TRANSLATION_VECTOR));
-        mAppSaturationController.setSaturationLevel(TEST_PACKAGE_NAME, mUserId, 30);
+        mAppSaturationController
+                .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId,
+                        30);
         AppSaturationController.computeGrayscaleTransformMatrix(.3f, mMatrix);
         verify(mColorTransformController, times(1))
                 .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
-        mAppSaturationController.setSaturationLevel(TEST_PACKAGE_NAME, mUserId, 100);
+        mAppSaturationController
+                .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId,
+                        100);
         AppSaturationController.computeGrayscaleTransformMatrix(1.0f, mMatrix);
         verify(mColorTransformController, times(2))
                 .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
@@ -97,19 +107,76 @@
     public void setSaturationLevel_updateLevel() {
         final WeakReference<ColorTransformController> ref = new WeakReference<>(
                 mColorTransformController);
-        mAppSaturationController.addColorTransformController(TEST_PACKAGE_NAME, mUserId, ref);
+        mAppSaturationController
+                .addColorTransformController(TEST_AFFECTED_PACKAGE_NAME, mUserId, ref);
         verify(mColorTransformController, never())
                 .applyAppSaturation(any(), eq(TRANSLATION_VECTOR));
-        mAppSaturationController.setSaturationLevel(TEST_PACKAGE_NAME, mUserId, 30);
+        mAppSaturationController
+                .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId,
+                        30);
         AppSaturationController.computeGrayscaleTransformMatrix(.3f, mMatrix);
         verify(mColorTransformController).applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
-        mAppSaturationController.setSaturationLevel(TEST_PACKAGE_NAME, mUserId, 70);
+        mAppSaturationController
+                .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId,
+                        70);
         AppSaturationController.computeGrayscaleTransformMatrix(.7f, mMatrix);
         verify(mColorTransformController, times(2))
                 .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
-        mAppSaturationController.setSaturationLevel(TEST_PACKAGE_NAME, mUserId, 100);
+        mAppSaturationController
+                .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId,
+                        100);
         AppSaturationController.computeGrayscaleTransformMatrix(1.0f, mMatrix);
         verify(mColorTransformController, times(3))
                 .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
     }
+
+    @Test
+    public void setSaturationLevel_multipleCallers_appliesStrongest() {
+        final WeakReference<ColorTransformController> ref = new WeakReference<>(
+                mColorTransformController);
+        mAppSaturationController
+                .addColorTransformController(TEST_AFFECTED_PACKAGE_NAME, mUserId, ref);
+        verify(mColorTransformController, never())
+                .applyAppSaturation(any(), eq(TRANSLATION_VECTOR));
+        mAppSaturationController
+                .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId,
+                        30);
+        AppSaturationController.computeGrayscaleTransformMatrix(0.3f, mMatrix);
+        verify(mColorTransformController, times(1))
+                .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
+        mAppSaturationController
+                .setSaturationLevel(TEST_CALLER_PACKAGE_NAME_TWO, TEST_AFFECTED_PACKAGE_NAME,
+                        mUserId,
+                        70);
+        verify(mColorTransformController, times(2))
+                .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
+    }
+
+    @Test
+    public void setSaturationLevel_multipleCallers_removingOneDoesNotAffectTheOther() {
+        final WeakReference<ColorTransformController> ref = new WeakReference<>(
+                mColorTransformController);
+        mAppSaturationController
+                .addColorTransformController(TEST_AFFECTED_PACKAGE_NAME, mUserId, ref);
+        verify(mColorTransformController, never())
+                .applyAppSaturation(any(), eq(TRANSLATION_VECTOR));
+        mAppSaturationController
+                .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId,
+                        70);
+        AppSaturationController.computeGrayscaleTransformMatrix(0.7f, mMatrix);
+        verify(mColorTransformController, times(1))
+                .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
+        mAppSaturationController
+                .setSaturationLevel(TEST_CALLER_PACKAGE_NAME_TWO, TEST_AFFECTED_PACKAGE_NAME,
+                        mUserId,
+                        30);
+        AppSaturationController.computeGrayscaleTransformMatrix(0.3f, mMatrix);
+        verify(mColorTransformController, times(2))
+                .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
+        mAppSaturationController
+                .setSaturationLevel(TEST_CALLER_PACKAGE_NAME_TWO, TEST_AFFECTED_PACKAGE_NAME,
+                        mUserId,
+                        100);
+        AppSaturationController.computeGrayscaleTransformMatrix(0.7f, mMatrix);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
index 12bdec6..0568be8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
@@ -22,6 +22,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER;
+import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
 
 import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS;
 import static com.android.server.wm.DisplayArea.Type.ANY;
@@ -72,7 +73,11 @@
         WindowManagerService wms = mSystemServices.getWindowManagerService();
         DisplayArea.Root root = new SurfacelessDisplayAreaRoot(wms);
         DisplayArea<WindowContainer> ime = new DisplayArea<>(wms, ABOVE_TASKS, "Ime");
-        DisplayArea<ActivityStack> tasks = new DisplayArea<>(wms, ANY, "Tasks");
+        DisplayContent displayContent = mock(DisplayContent.class);
+        TaskDisplayArea taskDisplayArea = new TaskDisplayArea(displayContent, wms, "Tasks",
+                FEATURE_DEFAULT_TASK_CONTAINER);
+        List<TaskDisplayArea> taskDisplayAreaList = new ArrayList<>();
+        taskDisplayAreaList.add(taskDisplayArea);
 
         final Feature foo;
         final Feature bar;
@@ -86,7 +91,7 @@
                         .all()
                         .except(TYPE_STATUS_BAR)
                         .build())
-                .build(wms, mock(DisplayContent.class), root, ime, tasks);
+                .build(wms, displayContent, root, ime, taskDisplayAreaList);
 
         policy.attachDisplayAreas();
 
@@ -98,9 +103,9 @@
         assertThat(policy.findAreaForToken(tokenOfType(TYPE_STATUS_BAR)),
                 is(not(decendantOfOneOf(policy.getDisplayAreas(bar)))));
 
-        assertThat(tasks,
+        assertThat(taskDisplayArea,
                 is(decendantOfOneOf(policy.getDisplayAreas(foo))));
-        assertThat(tasks,
+        assertThat(taskDisplayArea,
                 is(decendantOfOneOf(policy.getDisplayAreas(bar))));
 
         assertThat(ime,
@@ -109,7 +114,8 @@
                 is(decendantOfOneOf(policy.getDisplayAreas(bar))));
 
         List<DisplayArea<?>> actualOrder = collectLeafAreas(root);
-        Map<DisplayArea<?>, Set<Integer>> zSets = calculateZSets(policy, root, ime, tasks);
+        Map<DisplayArea<?>, Set<Integer>> zSets = calculateZSets(policy, root, ime,
+                taskDisplayArea);
         actualOrder = actualOrder.stream().filter(zSets::containsKey).collect(toList());
 
         Map<DisplayArea<?>, Integer> expectedByMinLayer = mapValues(zSets,
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java
index 4e4627b..6834ee5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java
@@ -77,8 +77,7 @@
 
         @Override
         public DisplayAreaPolicy instantiate(WindowManagerService wmService, DisplayContent content,
-                DisplayArea.Root root, DisplayArea<? extends WindowContainer> imeContainer,
-                TaskDisplayArea taskDisplayArea) {
+                DisplayArea.Root root, DisplayArea<? extends WindowContainer> imeContainer) {
             throw new RuntimeException("test stub");
         }
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index 618e608..880c486 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -54,17 +54,6 @@
     }
 
     @Test
-    public void testDisplayArea_positionChanged_throwsIfIncompatibleSibling() {
-        WindowManagerService wms = mWmsRule.getWindowManagerService();
-        DisplayArea<WindowContainer> parent = new SurfacelessDisplayArea<>(wms, ANY, "Parent");
-        DisplayArea<WindowContainer> child1 = new DisplayArea<>(wms, ANY, "Child1");
-        DisplayArea<WindowContainer> child2 = new DisplayArea<>(wms, ANY, "Child2");
-
-        parent.addChild(child1, 0);
-        assertThrows(IllegalStateException.class, () -> parent.addChild(child2, 0));
-    }
-
-    @Test
     public void testType_typeOf() {
         WindowManagerService wms = mWmsRule.getWindowManagerService();
 
@@ -87,10 +76,10 @@
         checkSiblings(BELOW_TASKS, ABOVE_TASKS);
         checkSiblings(ANY, ABOVE_TASKS);
         checkSiblings(ABOVE_TASKS, ABOVE_TASKS);
+        checkSiblings(ANY, ANY);
 
         assertThrows(IllegalStateException.class, () -> checkSiblings(ABOVE_TASKS, BELOW_TASKS));
         assertThrows(IllegalStateException.class, () -> checkSiblings(ABOVE_TASKS, ANY));
-        assertThrows(IllegalStateException.class, () -> checkSiblings(ANY, ANY));
         assertThrows(IllegalStateException.class, () -> checkSiblings(ANY, BELOW_TASKS));
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index 9625ffd..a708533 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -51,10 +51,10 @@
 import org.junit.runner.RunWith;
 
 /**
- * Tests for the {@link DisplayContent.TaskStackContainers} container in {@link DisplayContent}.
+ * Tests for the {@link TaskDisplayArea} container.
  *
  * Build/Install/Run:
- *  atest WmTests:TaskStackContainersTests
+ *  atest WmTests:TaskDisplayAreaTests
  */
 @SmallTest
 @Presubmit
@@ -154,8 +154,9 @@
                 ACTIVITY_TYPE_STANDARD, mDisplayContent);
         final Task newStack = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, mDisplayContent);
-        doReturn(newStack).when(mDisplayContent.mTaskContainers).createStack(anyInt(),
-                anyInt(), anyBoolean(), any(), any(), anyBoolean());
+        final TaskDisplayArea taskDisplayArea = candidateTask.getDisplayArea();
+        doReturn(newStack).when(taskDisplayArea).createStack(anyInt(), anyInt(), anyBoolean(),
+                any(), any(), anyBoolean());
 
         final int type = ACTIVITY_TYPE_STANDARD;
         assertGetOrCreateStack(WINDOWING_MODE_FULLSCREEN, type, candidateTask,
@@ -186,7 +187,7 @@
 
     private void assertGetOrCreateStack(int windowingMode, int activityType, Task candidateTask,
             boolean reuseCandidate) {
-        final TaskDisplayArea taskDisplayArea = (TaskDisplayArea) candidateTask.getParent();
+        final TaskDisplayArea taskDisplayArea = candidateTask.getDisplayArea();
         final ActivityStack stack = taskDisplayArea.getOrCreateStack(windowingMode, activityType,
                 false /* onTop */, null /* intent */, candidateTask /* candidateTask */);
         assertEquals(reuseCandidate, stack == candidateTask);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index 18737c2..57c7504 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -43,7 +43,9 @@
         // hard-code to FULLSCREEN for tests.
         setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         spyOn(this);
-        spyOn(mTaskContainers);
+        for (int i = getTaskDisplayAreaCount() - 1; i >= 0; --i) {
+            spyOn(getTaskDisplayAreaAt(i));
+        }
 
         final DisplayRotation displayRotation = getDisplayRotation();
         spyOn(displayRotation);
diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
index d5851d8..0c25cfb 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
@@ -34,18 +34,21 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
+import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.IoThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.pm.BackgroundDexOptService;
 import com.android.server.wm.ActivityMetricsLaunchObserver;
 import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto;
 import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature;
 import com.android.server.wm.ActivityMetricsLaunchObserverRegistry;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
+import java.util.ArrayList;
 import java.util.concurrent.TimeUnit;
 import java.util.HashMap;
 
@@ -286,6 +289,7 @@
 
     private final AppLaunchObserver mAppLaunchObserver = new AppLaunchObserver();
     private final EventSequenceValidator mEventSequenceValidator = new EventSequenceValidator();
+    private final DexOptPackagesUpdated mDexOptPackagesUpdated = new DexOptPackagesUpdated();
     private boolean mRegisteredListeners = false;
 
     private void registerInProcessListenersLocked() {
@@ -308,9 +312,22 @@
         launchObserverRegistry.registerLaunchObserver(mAppLaunchObserver);
         launchObserverRegistry.registerLaunchObserver(mEventSequenceValidator);
 
+        BackgroundDexOptService.addPackagesUpdatedListener(mDexOptPackagesUpdated);
+
+
         mRegisteredListeners = true;
     }
 
+    private class DexOptPackagesUpdated implements BackgroundDexOptService.PackagesUpdatedListener {
+        @Override
+        public void onPackagesUpdated(ArraySet<String> updatedPackages) {
+            String[] updated = updatedPackages.toArray(new String[0]);
+            for (String packageName : updated) {
+                Log.d(TAG, "onPackagesUpdated: " + packageName);
+            }
+        }
+    }
+
     private class AppLaunchObserver implements ActivityMetricsLaunchObserver {
         // We add a synthetic sequence ID here to make it easier to differentiate new
         // launch sequences on the native side.
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 327e8b3..56f3c3e 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1147,6 +1147,21 @@
             "support_ims_conference_event_package_bool";
 
     /**
+     * Determines whether processing of conference event package data received on a device other
+     * than the conference host is supported.
+     * <p>
+     * When a device A merges calls B and C into a conference it is considered the conference host
+     * and B and C are considered the conference peers.
+     * <p>
+     * When {@code true}, the conference peer will display the conference state if it receives
+     * conference event package data from the network.  When {@code false}, the conference peer will
+     * ignore conference event package data received from the network.
+     * @hide
+     */
+    public static final String KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_ON_PEER_BOOL =
+            "support_ims_conference_event_package_on_peer_bool";
+
+    /**
      * Determines whether High Definition audio property is displayed in the dialer UI.
      * If {@code false}, remove the HD audio property from the connection so that HD audio related
      * UI is not displayed. If {@code true}, keep HD audio property as it is configured.
@@ -1163,6 +1178,25 @@
             "support_ims_conference_call_bool";
 
     /**
+     * Determines whether the device will locally disconnect an IMS conference when the participant
+     * count drops to zero.  When {@code true}, it is assumed the carrier does NOT disconnect a
+     * conference when the participant count drops to zero and that the device must do this by
+     * disconnecting the conference locally.  When {@code false}, it is assumed that the carrier
+     * is responsible for disconnecting the conference when there are no longer any participants
+     * present.
+     * <p>
+     * Note: both {@link #KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL} and
+     * {@link #KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL} must be true for this configuration to
+     * have any effect.
+     * <p>
+     * Defaults to {@code false}, meaning the carrier network is responsible for disconnecting an
+     * empty IMS conference.
+     * @hide
+     */
+    public static final String KEY_LOCAL_DISCONNECT_EMPTY_IMS_CONFERENCE_BOOL =
+            "local_disconnect_empty_ims_conference_bool";
+
+    /**
      * Determines whether video conference calls are supported by a carrier.  When {@code true},
      * video calls can be merged into conference calls, {@code false} otherwiwse.
      * <p>
@@ -3778,8 +3812,10 @@
         sDefaults.putBoolean(KEY_SUPPORT_ADD_CONFERENCE_PARTICIPANTS_BOOL, false);
         sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true);
+        sDefaults.putBoolean(KEY_LOCAL_DISCONNECT_EMPTY_IMS_CONFERENCE_BOOL, false);
         sDefaults.putBoolean(KEY_SUPPORT_MANAGE_IMS_CONFERENCE_CALL_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL, true);
+        sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_ON_PEER_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL, false);
         sDefaults.putBoolean(KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL, false);
         sDefaults.putInt(KEY_IMS_CONFERENCE_SIZE_LIMIT_INT, 5);
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index 2b1d9e5..18e2592 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -113,6 +113,7 @@
     public static final int EVENT_5G_TIMER_HYSTERESIS = BASE + 53;
     public static final int EVENT_5G_TIMER_WATCHDOG = BASE + 54;
     public static final int EVENT_CARRIER_CONFIG_CHANGED = BASE + 55;
+    public static final int EVENT_SIM_STATE_UPDATED = BASE + 56;
 
     /***** Constants *****/
 
diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
index 6e9dc8e..3f8261d 100644
--- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -17,6 +17,8 @@
 package android.net;
 
 import static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
+import static android.net.NetworkCapabilities.MAX_TRANSPORT;
+import static android.net.NetworkCapabilities.MIN_TRANSPORT;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS;
@@ -32,10 +34,12 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
 import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES;
+import static android.net.NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED;
 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.TRANSPORT_WIFI_AWARE;
 import static android.net.NetworkCapabilities.UNRESTRICTED_CAPABILITIES;
 
 import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
@@ -45,10 +49,15 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.net.wifi.aware.DiscoverySession;
+import android.net.wifi.aware.PeerHandle;
+import android.net.wifi.aware.WifiAwareNetworkSpecifier;
 import android.os.Build;
+import android.os.Process;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArraySet;
 
@@ -61,6 +70,7 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mockito;
 
 import java.util.Arrays;
 import java.util.Set;
@@ -74,6 +84,9 @@
     @Rule
     public DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule();
 
+    private DiscoverySession mDiscoverySession = Mockito.mock(DiscoverySession.class);
+    private PeerHandle mPeerHandle = Mockito.mock(PeerHandle.class);
+
     private boolean isAtLeastR() {
         // BuildCompat.isAtLeastR() is used to check the Android version before releasing Android R.
         // Build.VERSION.SDK_INT > Build.VERSION_CODES.Q is used to check the Android version after
@@ -685,4 +698,238 @@
         assertEquals(TRANSPORT_VPN, transportTypes[2]);
         assertEquals(TRANSPORT_TEST, transportTypes[3]);
     }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+    public void testTelephonyNetworkSpecifier() {
+        final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(1);
+        final NetworkCapabilities nc1 = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_WIFI)
+                .setNetworkSpecifier(specifier)
+                .build();
+        assertEquals(specifier, nc1.getNetworkSpecifier());
+        try {
+            final NetworkCapabilities nc2 = new NetworkCapabilities.Builder()
+                    .setNetworkSpecifier(specifier)
+                    .build();
+            fail("Must have a single transport type. Without transport type or multiple transport"
+                    + " types is invalid.");
+        } catch (IllegalStateException expected) { }
+    }
+
+    @Test
+    public void testWifiAwareNetworkSpecifier() {
+        final NetworkCapabilities nc = new NetworkCapabilities()
+                .addTransportType(TRANSPORT_WIFI_AWARE);
+        // If NetworkSpecifier is not set, the default value is null.
+        assertNull(nc.getNetworkSpecifier());
+        final WifiAwareNetworkSpecifier specifier = new WifiAwareNetworkSpecifier.Builder(
+                mDiscoverySession, mPeerHandle).build();
+        nc.setNetworkSpecifier(specifier);
+        assertEquals(specifier, nc.getNetworkSpecifier());
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+    public void testAdministratorUidsAndOwnerUid() {
+        // Test default owner uid.
+        // If the owner uid is not set, the default value should be Process.INVALID_UID.
+        final NetworkCapabilities nc1 = new NetworkCapabilities.Builder().build();
+        assertEquals(Process.INVALID_UID, nc1.getOwnerUid());
+        // Test setAdministratorUids and getAdministratorUids.
+        final int[] administratorUids = {1001, 10001};
+        final NetworkCapabilities nc2 = new NetworkCapabilities.Builder()
+                .setAdministratorUids(administratorUids)
+                .build();
+        assertTrue(Arrays.equals(administratorUids, nc2.getAdministratorUids()));
+        // Test setOwnerUid and getOwnerUid.
+        // The owner UID must be included in administrator UIDs, or throw IllegalStateException.
+        try {
+            final NetworkCapabilities nc3 = new NetworkCapabilities.Builder()
+                    .setOwnerUid(1001)
+                    .build();
+            fail("The owner UID must be included in administrator UIDs.");
+        } catch (IllegalStateException expected) { }
+        final NetworkCapabilities nc4 = new NetworkCapabilities.Builder()
+                .setAdministratorUids(administratorUids)
+                .setOwnerUid(1001)
+                .build();
+        assertEquals(1001, nc4.getOwnerUid());
+        try {
+            final NetworkCapabilities nc5 = new NetworkCapabilities.Builder()
+                    .setAdministratorUids(null)
+                    .build();
+            fail("Should not set null into setAdministratorUids");
+        } catch (NullPointerException expected) { }
+    }
+
+    @Test
+    public void testLinkBandwidthKbps() {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        // The default value of LinkDown/UpstreamBandwidthKbps should be LINK_BANDWIDTH_UNSPECIFIED.
+        assertEquals(LINK_BANDWIDTH_UNSPECIFIED, nc.getLinkDownstreamBandwidthKbps());
+        assertEquals(LINK_BANDWIDTH_UNSPECIFIED, nc.getLinkUpstreamBandwidthKbps());
+        nc.setLinkDownstreamBandwidthKbps(512);
+        nc.setLinkUpstreamBandwidthKbps(128);
+        assertEquals(512, nc.getLinkDownstreamBandwidthKbps());
+        assertNotEquals(128, nc.getLinkDownstreamBandwidthKbps());
+        assertEquals(128, nc.getLinkUpstreamBandwidthKbps());
+        assertNotEquals(512, nc.getLinkUpstreamBandwidthKbps());
+    }
+
+    @Test
+    public void testSignalStrength() {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        // The default value of signal strength should be SIGNAL_STRENGTH_UNSPECIFIED.
+        assertEquals(SIGNAL_STRENGTH_UNSPECIFIED, nc.getSignalStrength());
+        nc.setSignalStrength(-80);
+        assertEquals(-80, nc.getSignalStrength());
+        assertNotEquals(-50, nc.getSignalStrength());
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+    public void testDeduceRestrictedCapability() {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        // Default capabilities don't have restricted capability.
+        assertFalse(nc.deduceRestrictedCapability());
+        // If there is a force restricted capability, then the network capabilities is restricted.
+        nc.addCapability(NET_CAPABILITY_OEM_PAID);
+        nc.addCapability(NET_CAPABILITY_INTERNET);
+        assertTrue(nc.deduceRestrictedCapability());
+        // Except for the force restricted capability, if there is any unrestricted capability in
+        // capabilities, then the network capabilities is not restricted.
+        nc.removeCapability(NET_CAPABILITY_OEM_PAID);
+        nc.addCapability(NET_CAPABILITY_CBS);
+        assertFalse(nc.deduceRestrictedCapability());
+        // Except for the force restricted capability, the network capabilities will only be treated
+        // as restricted when there is no any unrestricted capability.
+        nc.removeCapability(NET_CAPABILITY_INTERNET);
+        assertTrue(nc.deduceRestrictedCapability());
+    }
+
+    private void assertNoTransport(NetworkCapabilities nc) {
+        for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) {
+            assertFalse(nc.hasTransport(i));
+        }
+    }
+
+    // Checks that all transport types from MIN_TRANSPORT to maxTransportType are set and all
+    // transport types from maxTransportType + 1 to MAX_TRANSPORT are not set when positiveSequence
+    // is true. If positiveSequence is false, then the check sequence is opposite.
+    private void checkCurrentTransportTypes(NetworkCapabilities nc, int maxTransportType,
+            boolean positiveSequence) {
+        for (int i = MIN_TRANSPORT; i <= maxTransportType; i++) {
+            if (positiveSequence) {
+                assertTrue(nc.hasTransport(i));
+            } else {
+                assertFalse(nc.hasTransport(i));
+            }
+        }
+        for (int i = MAX_TRANSPORT; i > maxTransportType; i--) {
+            if (positiveSequence) {
+                assertFalse(nc.hasTransport(i));
+            } else {
+                assertTrue(nc.hasTransport(i));
+            }
+        }
+    }
+
+    @Test
+    public void testMultipleTransportTypes() {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        assertNoTransport(nc);
+        // Test adding multiple transport types.
+        for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) {
+            nc.addTransportType(i);
+            checkCurrentTransportTypes(nc, i, true /* positiveSequence */);
+        }
+        // Test removing multiple transport types.
+        for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) {
+            nc.removeTransportType(i);
+            checkCurrentTransportTypes(nc, i, false /* positiveSequence */);
+        }
+        assertNoTransport(nc);
+        nc.addTransportType(TRANSPORT_WIFI);
+        assertTrue(nc.hasTransport(TRANSPORT_WIFI));
+        assertFalse(nc.hasTransport(TRANSPORT_VPN));
+        nc.addTransportType(TRANSPORT_VPN);
+        assertTrue(nc.hasTransport(TRANSPORT_WIFI));
+        assertTrue(nc.hasTransport(TRANSPORT_VPN));
+        nc.removeTransportType(TRANSPORT_WIFI);
+        assertFalse(nc.hasTransport(TRANSPORT_WIFI));
+        assertTrue(nc.hasTransport(TRANSPORT_VPN));
+        nc.removeTransportType(TRANSPORT_VPN);
+        assertFalse(nc.hasTransport(TRANSPORT_WIFI));
+        assertFalse(nc.hasTransport(TRANSPORT_VPN));
+        assertNoTransport(nc);
+    }
+
+    @Test
+    public void testAddAndRemoveTransportType() {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        try {
+            nc.addTransportType(-1);
+            fail("Should not set invalid transport type into addTransportType");
+        } catch (IllegalArgumentException expected) { }
+        try {
+            nc.removeTransportType(-1);
+            fail("Should not set invalid transport type into removeTransportType");
+        } catch (IllegalArgumentException e) { }
+    }
+
+    private class TestTransportInfo implements TransportInfo {
+        TestTransportInfo() {
+        }
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+    public void testBuilder() {
+        final int ownerUid = 1001;
+        final int signalStrength = -80;
+        final int requestUid = 10100;
+        final int[] administratorUids = {ownerUid, 10001};
+        final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(1);
+        final TestTransportInfo transportInfo = new TestTransportInfo();
+        final String ssid = "TEST_SSID";
+        final String packageName = "com.google.test.networkcapabilities";
+        final NetworkCapabilities nc = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_WIFI)
+                .addTransportType(TRANSPORT_CELLULAR)
+                .removeTransportType(TRANSPORT_CELLULAR)
+                .addCapability(NET_CAPABILITY_EIMS)
+                .addCapability(NET_CAPABILITY_CBS)
+                .removeCapability(NET_CAPABILITY_CBS)
+                .setAdministratorUids(administratorUids)
+                .setOwnerUid(ownerUid)
+                .setLinkDownstreamBandwidthKbps(512)
+                .setLinkUpstreamBandwidthKbps(128)
+                .setNetworkSpecifier(specifier)
+                .setTransportInfo(transportInfo)
+                .setSignalStrength(signalStrength)
+                .setSsid(ssid)
+                .setRequestorUid(requestUid)
+                .setRequestorPackageName(packageName)
+                .build();
+        assertEquals(1, nc.getTransportTypes().length);
+        assertEquals(TRANSPORT_WIFI, nc.getTransportTypes()[0]);
+        assertTrue(nc.hasCapability(NET_CAPABILITY_EIMS));
+        assertFalse(nc.hasCapability(NET_CAPABILITY_CBS));
+        assertTrue(Arrays.equals(administratorUids, nc.getAdministratorUids()));
+        assertEquals(ownerUid, nc.getOwnerUid());
+        assertEquals(512, nc.getLinkDownstreamBandwidthKbps());
+        assertNotEquals(128, nc.getLinkDownstreamBandwidthKbps());
+        assertEquals(128, nc.getLinkUpstreamBandwidthKbps());
+        assertNotEquals(512, nc.getLinkUpstreamBandwidthKbps());
+        assertEquals(specifier, nc.getNetworkSpecifier());
+        assertEquals(transportInfo, nc.getTransportInfo());
+        assertEquals(signalStrength, nc.getSignalStrength());
+        assertNotEquals(-50, nc.getSignalStrength());
+        assertEquals(ssid, nc.getSsid());
+        assertEquals(requestUid, nc.getRequestorUid());
+        assertEquals(packageName, nc.getRequestorPackageName());
+        // Cannot assign null into NetworkCapabilities.Builder
+        try {
+            final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(null);
+            fail("Should not set null into NetworkCapabilities.Builder");
+        } catch (NullPointerException expected) { }
+        assertEquals(nc, new NetworkCapabilities.Builder(nc).build());
+    }
 }